hmssql
Safe HaskellNone
LanguageHaskell2010

Hmssql.Connection.Internal

Description

Connection state, packet I/O, and the incremental token reader.

Everything here is protected by the connection's reentrant mutex. A request creates an ActiveResponse; tokens are pulled from it one at a time, and starting a new request first cancels and drains any unfinished response so the connection can never be left mid-message.

A request and its response travel on a Session. A connection has one, connPrimary, unless it negotiated MARS (connMars): then each request takes an SMP session of its own (Hmssql.Tds.Smp), so several responses can be open at once, and a new request leaves the others alone. The backend, the transport, the protocol state (connContext, the transaction descriptor included), the mutex and the broken flag stay the connection's: the sessions are one SQL session on the server.

Interruptions (asynchronous exceptions) are handled by two rules:

  • A send that doesn't complete breaks the connection (sendBytes): the server may hold part of a message, and nothing here tries to recover from that.
  • A read that is interrupted leaves the connection usable (nextToken): an Attention is sent at once, which cancels the statement, and the next request drains the canceled response up to the server's acknowledgement (finishResponse). If that drain is itself interrupted, the connection is broken too.

A Connection value is a handle, a lease on the connection: every field but connLease is shared or immutable, so a record copy is another handle to the same connection. release invalidates the handle it was given (invalidateLease) and renewLease makes a new one; everything that takes the lock, and every row a stream yields, checks the handle first (checkLease).

Applications use Hmssql.Connection; nothing here is stable.

Synopsis

Documentation

data Connection Source #

An open connection to a server. It belongs to one thread at a time; Hmssql.Connection has the rules.

Constructors

Connection 

Fields

data Transport Source #

How the connection's bytes go to the backend: as they are, or through a TLS session.

data Session Source #

What one request at a time travels on: the connection itself, or under MARS one of its SMP sessions. A session is busy while it has a current response, that is, until the response's end has been read (StepEnd), whether by its consumer or by a drain after it was canceled.

Constructors

Session 

Fields

data SmpSession Source #

An SMP session's state on the side of the threads that use the connection.

Constructors

SmpSession 

Fields

data SmpLink Source #

An SMP session's state shared with the reader thread, which refers to nothing else of the session (see startReader).

Constructors

SmpLink 

Fields

  • linkIncoming :: !(TQueue ByteString)

    The TDS packets received, whole, in order. At most a window of them: the server sends no further than it was told.

  • linkSendHighWater :: !(TVar Word32)

    The highest sequence number the server accepts (HighWaterForSend): the window of the last packet received on the session.

  • linkRecvSeq :: !(TVar Word32)

    The sequence number of the last DATA packet received (SeqNumForRecv).

  • linkRecvHighWater :: !(TVar Word32)

    The highest sequence number accepted from the server (HighWaterForRecv): the packets consumed plus the window. Raised by the consumer before it tells the server.

  • linkEoms :: !(TVar Int)

    How many packets marked end-of-message have been received.

  • linkClosing :: !(TVar Bool)

    A FIN was sent; the server's FIN in reply frees the identifier.

data Mars Source #

What a MARS connection has beyond a plain one.

Constructors

Mars 

Fields

data ActiveResponse Source #

The server's reply to one request.

Constructors

ActiveResponse 

Fields

data ReadState Source #

What has been received but not yet returned: whether the message's last packet has arrived, and where the parse has got to.

data ReadPosition Source #

Constructors

AtToken !ByteString

Between tokens, with the unconsumed payload.

Reading (Stream Await IO ReadEvent)

Normalized suspension, ready for a packet or a consumer outcome.

Parked !ByteString (Parser ())

A generic cell pull returned; the recipe drains its remaining row.

newConnection :: Backend -> TdsContext -> (ServerMessage -> IO ()) -> IO Connection Source #

A connection with no session setup; set connSessionSetup afterwards.

startReader :: Connection -> IO () Source #

Move transport reads onto a dedicated thread. Consumers then block on a TMVar, which can be interrupted without losing bytes or TLS state. Call once the transport is final (after login).

When the reader stops, it records why in connEnded: the ConnectionClosed that recvDirect threw, or ConnectionBroken if it died of anything else (it is never interrupted except by closeConnection, which records its own reason first, or by the finalizer below). It never passes an exception on as data, so a reader killed by closeConnection doesn't look to the thread waiting for a response as if that thread itself had been killed.

The thread refers to the transport, connIncoming and connEnded only, never to the whole Connection: it is always reachable (blocked in a read), so if it kept the connection reachable, a connection dropped without close would never be found garbage, and the finalizer below, which stops the thread and closes the socket, would never run.

Small chunks are copied into shared buffers (copyIntoArena) before they are passed on.

On a MARS connection (enableMars came first) the thread cuts what it receives into SMP packets instead and hands each session its TDS packets (demultiplex); it then refers to the session registry, and still not to the connection.

enableMars :: Connection -> Int -> IO Connection Source #

Start SMP on a connection that negotiated MARS, once it has logged in (and run its session setup) in plain TDS: open session 0, with the given window (at least 4 packets). Before startReader. The connection returned is the one to use; the one given is no longer.

allSessions :: Connection -> IO [Session] Source #

Every session of the connection: the one, or under MARS the open SMP sessions, in the order of their identifiers.

closeConnection :: Connection -> IO () -> IO () Source #

Mark the connection closed by the client, stop the reader thread, run goodbye (a TLS close_notify) unless a send is in progress, and close the socket. A thread waiting for a response or sending, and every later use, then fails with ConnectionClosedBy ClosedByClient.

A send in progress (or a goodbye, from another closeConnection) may wait for the server to read, which may be never, so it is not waited for as it is: the backend is shut down, which makes the send fail at once, and closed only once the send has given up the send lock. Closing a socket under a thread that is sending on it would be unsafe: that thread may read the descriptor just before the close and use it just after, when the number may already belong to another socket. (Or start to wait on it after the close: GHC's IO manager (before 9.14) then keeps a stale registration for the number, which on macOS makes closing the number's next socket fail; see closeSocket.) The wait for the send lock is interruptible; the backend is then shut down but not closed, until the next closeConnection (or the finalizer).

The backend is closed only once the reader thread has stopped: if it were still reading, it could go on to read from whatever socket the next connection is given with the same descriptor.

The connection is kept reachable throughout (keepAlive), as a send keeps it: past its first lines this refers only to the fields it uses, so a connection whose last handle was the caller's could otherwise be found garbage half-way, and the finalizer of startReader would close the backend under the goodbye. The close_notify was then lost, its send failing on the closed socket; a send that had read the descriptor just before the close could have written it to whatever socket was given the number next.

releaseTransport :: Connection -> IO () Source #

After closeConnection: drop what only a live transport needs, so that a closed connection that is still referenced (by a pool, a stream, a thread) keeps little alive. A TLS context keeps its record buffers and the parameters it was made with, the certificate store among them: about 3 MB with the system store. The transport becomes the (closed) backend; nothing uses it any more, since the reader thread has stopped and sends and reads check connEnded first. A thread that was sending keeps the TLS context it had until its send fails.

The current response goes too: a response still arriving (a stream read part-way) keeps the rest of the packet or chunk it stopped in, up to a whole 32 KiB arena (copyIntoArena). Nothing reads it again, since nextToken and startRequest check connEnded first; a stream or thread that still has the response keeps it as long as it does.

closeSocket :: Socket -> IO () Source #

Close a socket, where a failure to close must not get in the way: in the cleanup after a failed connect (where it would replace the exception that made it fail), in close, and in the finalizer of a connection dropped without it. It throws no synchronous exception, and the descriptor is closed even when close fails, which it can:

close marks the socket closed, then closes the descriptor with GHC's closeFdWith, which first removes it from the IO manager. With kqueue (macOS, BSD), that throws kevent: does not exist when the IO manager's table has a registration for the descriptor number that the kernel doesn't. Before GHC 9.14, a thread that starts to wait on a descriptor just after another thread closed it leaves one (checked on 9.6 to 9.12), and the next socket to get the number then fails to close. closeFdWith gives up before closing the descriptor (in 9.14 too), which would stay open (and the connection with it) for the life of the process, since the socket is already marked closed. So on failure the descriptor is closed here directly: not through closeFdWith again, which would block for good (after that failure it leaves the IO manager's lock for the descriptor taken, and anything waiting on a descriptor under the same lock hangs; nothing here can undo that). Both are bugs in GHC's kqueue IO manager.

Runs masked, so that an asynchronous exception can't come between close marking the socket closed and closing the descriptor.

closeSocketWith :: (Socket -> IO ()) -> Socket -> IO () Source #

closeSocket with the given way to close the socket, standing in for close. If it fails after marking the socket closed (as close does when closeFdWith fails), the descriptor is closed directly; if it fails before, the socket is left as it is (network's finalizer closes it). A synchronous exception is not rethrown.

socketCloser :: IORef (Socket -> IO ()) Source #

How closeSocket closes a socket: close. Tests replace it to make closing fail.

connectionContext :: Connection -> IO TdsContext Source #

The protocol state negotiated for the connection: the TDS version, the packet size the server granted, whether a transaction is open, the session's collation.

connectionMars :: Connection -> Bool Source #

Whether the connection negotiated MARS (connectMars): several responses can be open on it at once, and a new request leaves an open stream alone. For code that is handed a Connection without the ConnectInfo it was opened with.

withConnectionLock :: Connection -> IO a -> IO a Source #

Hold the connection's reentrant lock. Refuses (with ReentrantCallback) a callback's attempt to use the connection: the callback's thread already holds the lock, so the attempt would otherwise interleave with the response being read. Refuses a released handle too (checkLease), once the lock is held, so a handle released while this call waited for the lock is refused.

checkLease :: Connection -> IO () Source #

Throw ConnectionReleased if this handle is not the connection's current one: it was released, or renewLease made a newer one.

invalidateLease :: Connection -> IO () Source #

Make every existing handle to the connection stale. The caller holds the lock.

renewLease :: Connection -> IO Connection Source #

A new handle to the connection, making every existing one stale. Its argument may be stale (that is what it is for), so it takes the mutex directly, not withConnectionLock: it waits for a request in progress on another handle rather than cutting in.

renewUsable :: Connection -> IO Connection Source #

renewLease, after throwing what a request would throw if the connection is unusable. The check comes first, so a refusal leaves the lease alone.

refuseUnusable :: Connection -> IO () Source #

Throw the connection's reason if it is broken, closed by the client, or ended by the server (which marks it broken too), as a request on it would. For the functions that would otherwise return normally with nothing to do (cancelActiveStatement, the transaction functions' checks), so that a normal return says the connection is not known to be unusable.

currentUnusableReason :: Connection -> IO (Maybe UnusableReason) Source #

Why the connection is known to be unusable, as refuseUnusable would throw it. It reads what is recorded and leaves it as it is.

markBroken :: Connection -> UnusableReason -> IO () Source #

Record why the connection is unusable. The first reason stays.

reasonFor :: (SomeException -> UnusableReason) -> SomeException -> UnusableReason Source #

The reason an exception gives by its own type, whatever was going on; the function says what to call any other exception.

resetFailure :: SomeException -> UnusableReason Source #

The reason for a failure of the request that carried a reset: the server's refusal, or a request that could not be finished.

throwSevere :: Connection -> Done -> Maybe Text -> IO a Source #

Throw SevereError for a failed DONE. After DONE_SRVERROR the server closes the connection, so it is broken from here on, as after a severity 20 error, without waiting for the reader thread to see the close.

transportError :: SomeException -> SomeException Source #

The typed form of a synchronous exception from the transport: an IOException or TLSException becomes ConnectionClosed. TLS reports the end of the stream (after a TCP EOF without close_notify, as when the server kills the session) as an error, but it is a close like any other. Anything else, async exceptions included, is returned unchanged.

recvSome :: Connection -> IO ByteString Source #

Read some bytes. Throws ConnectionClosed on EOF, and after closeConnection.

recvExactly :: Connection -> Int -> IO ByteString Source #

Read exactly n bytes. Bytes stay in sessRecvBuffer until all n are available, so an interrupted call loses nothing.

newBufferedRecv :: Backend -> IO (Int -> IO ByteString) Source #

A receive function for a TLS backend: exactly the bytes asked for, or fewer at the end of the stream (as TLS.getBackend's), copied out of a 32 KiB buffer that is filled by receiving as much as the backend has. tls reads each record in two calls, its 5-byte header and then its body, which TLS.getBackend made two system calls; here one receive usually covers several records. Each call allocates what it returns, as TLS.getBackend's did; the buffer is the connection's for its life. One thread reads at a time (the connecting thread during the handshake, the reader thread after). The buffer is kept alive over each receive with withForeignPtr, as in recvIntoArena.

newtype SendGather Source #

Where the TLS records of a message go: a TLS backend's send (gatheredSend) writes each record to the backend at once, unless sendWith is sending a message, when it gathers them into writes of up to gatherLimit bytes. Records still hold one TDS packet each (sendFramed); only the system calls change (a 4 MB parameter took 525 sends of one 8 KB record, which cost the client more CPU than tls's encryption of it). Every backend send goes through the MVar, so records tls sends on its own while a message is being gathered (the reader thread's answer to a KeyUpdate, an alert) are gathered too, in order.

Constructors

SendGather (MVar Gathered) 

data Gathered Source #

Constructors

NotGathering 
Gathering ![[ByteString]] !Int

Records not yet written, newest first, each a write of one piece (backendSendMany), and their total length.

gatheredSend :: SendGather -> Backend -> ByteString -> IO () Source #

A TLS backend's send over the connection's backend: at once, or gathered while sendWith is sending a message. It refers to the backend and the gather only, never to the connection (see startReader).

sendBytes :: Connection -> [ByteString] -> IO () Source #

Send a message, as chunks of whole packets (splitIntoPackets, frameSegments). Returns normally if and only if the whole message was sent; otherwise the connection is broken.

The chunks are produced as they are sent (sendFramed frames them lazily), so the caller must have evaluated the message body in full first: a lazy encoding failure (an error inside a Put) must fail before anything goes out, not cut a message short. The send runs with asynchronous exceptions masked, so it can only be interrupted while it actually blocks: waiting for the transport to take the bytes (a peer that doesn't read), or for the TLS session's write lock. Any exception that escapes it, asynchronous or not, marks the connection broken (the server may hold part of a message, and neither it nor the TLS session can be told to forget it), shuts the socket down so the reader thread ends and a later closeConnection is clean, and is rethrown: an interruption reaches the code that sent it, and the connection then throws ConnectionBroken wrapping it.

A transport failure is thrown as ConnectionClosed (see transportError), and nothing is sent once the connection has ended: if the reader thread has seen the server close the connection (as when the session was killed while idle), or closeConnection was called, that is the reason given.

sendFramed :: Connection -> Session -> TdsContext -> PacketType -> PacketStatus -> ByteString -> IO () Source #

Frame a message body and send it (sendBytes), in chunks that suit the transport: on a plain socket, up to 64 KiB of whole packets per write; over TLS, one packet per chunk, since SQL Server (2025, strict encryption) resets the connection when a TLS record holds more than one TDS packet (the records are gathered into writes of up to 64 KiB instead, SendGather). The body must be evaluated in full already.

On an SMP session each packet goes out as an SMP DATA packet (sendSmpFrames), and the send lock is held per write, not for the message; the rest is the same: masked, and any exception out of it breaks the connection.

sendPackets :: Connection -> Session -> TdsContext -> PacketType -> (PacketWriter -> IO a) -> IO (Either SomeException a) Source #

Send a message whose packets are produced as they are sent: the producer gets a PacketWriter for a message of this packet type, and the message is what it writes, ending when it returns. For a message too long to build first (a bulk load), or one produced from an effectful source.

Like sendBytes, it holds the send lock and runs masked, so the producer is interrupted only where it blocks (a send that waits for the transport, or its own source). The packets go out as sendFramed sends them: up to 64 KiB of whole packets per socket write, or one TLS record each, gathered.

Returns Right the producer's result once every packet it sent has been handed to the transport. A synchronous exception from the producer itself (not from a send) leaves the transport at a packet boundary, with the message unfinished: it is returned as Left, and the connection is not broken, for the caller to cancel the message (startStreamingRequest does). A failed send, or an asynchronous exception (which may land inside a send), breaks the connection and is rethrown, as for sendBytes.

readPacket :: Connection -> IO (Header, ByteString) Source #

Read one packet. Interrupted while waiting for it, it loses nothing; the caller must run it masked if the packet must not be lost once read (as nextToken does).

This reads the primary session (readSessionPacket): the connection's one, or SMP session 0.

sendMessage :: Connection -> PacketType -> Put -> IO () Source #

Frame and send a complete message.

runBody :: Put -> ByteString Source #

A message body. binary's runPut starts with a 4 KiB buffer, which GHC allocates as a large object (taking the storage manager's lock) for every request, though most bodies are a few dozen bytes; this starts with 512 bytes and continues in 32 KiB chunks. The chunks aren't trimmed: the body is copied into packets anyway (frameSegments).

startRequest :: Connection -> PacketType -> (TdsContext -> ByteString) -> IO ActiveResponse Source #

Send a request and start reading its response. Any unfinished previous response is canceled and drained first; on a MARS connection the request takes a session of its own instead, and the other responses stay open (prepareForRequest). A pending requestReset is carried out first (see there).

sourceAction :: Connection -> IO a -> IO a Source #

User code producing request data may not start a request on this connection.

bytesBody :: (TdsContext -> ByteString) -> TdsContext -> PacketWriter -> IO () Source #

A request body already in memory, as the request writer takes it. The body's length is forced before anything is written, so a lazy encoding failure fails the request before any of it is sent.

startRequestFor :: Connection -> Maybe Text -> PacketType -> (TdsContext -> PacketWriter -> IO ()) -> IO ActiveResponse Source #

startRequest, with the request's SQL text: what names the response if it is found unfinished (busyStatements).

startStreamingRequest :: Connection -> Maybe Text -> PacketType -> (PacketWriter -> IO ()) -> IO ActiveResponse Source #

startRequest for a message produced packet by packet as it is sent (sendPackets): the producer gets a PacketWriter, and the message ends when it returns. A pending requestReset is left pending (the message isn't a SQL batch or RPC).

If the producer fails (synchronously) with the message unfinished, the request is canceled: an Attention is sent, and the server's acknowledgement read (as after an interrupted read, nextToken), so the connection stays usable; the producer's exception is then rethrown. A failed or interrupted send breaks the connection, as any send does.

withReservedSession :: Connection -> IO a -> IO a Source #

Run the action with every request it makes sent on one session, taken for it; the lock is held throughout. For an exchange that spans several requests which the server ties to a session: a bulk load's INSERT BULK, its BULK LOAD message, and the Attention that recovers a failed load. As without MARS, each request inside first finishes the session's previous response. Without MARS it is withConnectionLock.

requestReset :: Connection -> IO () Source #

Reset the session before the next SQLBatch, RPC or transaction manager request: the session returns to its state right after login (MS-TDS 2.2.3.1.2, RESETCONNECTION), and the session setup runs again. With no session setup, the next request itself carries the RESETCONNECTION bit, so the reset costs nothing extra; otherwise it is preceded by one batch that carries the bit and the session setup.

Takes the connection lock, so a reset requested by another thread waits for whatever holds the connection (a request being read, or a whole withTransaction) and applies to the request after it.

resetWithSessionSetup :: Connection -> IO () Source #

Reset the session and run the session setup again, in one SQLBatch that carries the RESETCONNECTION bit, read to the end. SET statements inside an RPC (such as sp_executesql) revert when it returns, and prefixing them to the caller's batch would break batches that must start with CREATE PROCEDURE and the like, so this costs a round trip of its own.

If reading the response fails or is interrupted, it is unknown how much of the setup ran, so the connection is marked broken; likewise if the setup fails on the server (as in connect, a session without its setup is not usable): with an ERROR token (SqlServerError), or with a DONE flagged as failed and no ERROR token (SevereError).

Unlike sp_reset_connection, RESETCONNECTION keeps the transaction isolation level (SQL Server 2025; Azure SQL Database puts READ COMMITTED back); nothing here restores it.

data RowRead a Source #

What to do with rows, as a response is read.

Constructors

ReadRows !(Parser a) !(IORef (Maybe (Either ReadFailure a)))

Decode each row: the prepared recipe (rowReader), which serves the whole result set, and the slot through which a row suspended across packets delivers its result.

WholeRows (Row -> a)

Keep each row as a Row of raw cells, for the token-level interface.

DiscardRows a

Skip the rows and keep the other tokens, output parameters included.

DrainRows a

Throw the whole response away, the values of output parameters included.

StopAtRow

Stop before a row, consuming nothing. A request waiting behind a reset reads only the reset's leading tokens, and reading a row there would mean keeping it for the consumer and parsing it a second time.

newRowRead :: Int -> CompiledRow a -> IO (RowRead a) Source #

Read rows with this compiled row decoder, given the bound on allocating ahead of a value's bytes. One RowRead serves a whole result set.

wholeRows :: RowRead Row Source #

Every row as a Row.

discardRows :: RowRead () Source #

Skip the rows, keeping the other tokens.

drainingRows :: RowRead () Source #

Discard token payloads too, including large RETURNVALUE values.

data RowBatch r Source #

The rows at the head of the response, decoded: what one nextStep returns when it finds rows.

Constructors

RowBatch 

Fields

data ResponseStep r Source #

What nextStep found next in the response.

Constructors

StepRows !(RowBatch r) 
StepToken !Token

A token other than a row.

StepEnd

The response is complete.

nextStep :: Connection -> ActiveResponse -> RowRead r -> IO (ResponseStep r) Source #

Read a batch of rows, a non-row token, or the end of the response. Complete rows are returned before waiting for another packet. A partial token remains as a continuation in the response, without re-reading bytes.

readPiece :: Connection -> ActiveResponse -> Parser a -> (a -> Maybe (Parser ())) -> IO a Source #

Pull a generic cell fragment, preserving a recipe for abandoning its row. The function says, from what was read, how to skip the rest of the row, or Nothing when the pull ended at a token boundary.

nextToken :: Connection -> ActiveResponse -> IO (Maybe Token) Source #

Read one raw token. An interrupted receive sends Attention eagerly unless the response has already arrived, and its continuation is retained for the drain. A malformed token breaks the connection.

checkCurrent :: Connection -> ActiveResponse -> IO () Source #

Throw ResultAbandoned unless this response is still its session's current one: not superseded by a later request (without MARS), canceled by an interruption or by cancelActiveResponse, drained, or closed with the connection (which throws its own reason); ConnectionReleased if the handle was released (checkLease). What nextStep checks before reading; a stream that holds rows of a batch checks it before yielding each, so that a superseded stream throws at once, not after the rows it had.

finishResponse :: Connection -> IO () Source #

Make sure no response is pending on the connection: finishSession for its session, or under MARS for every session that has one. Under MARS the Attentions all go out first and the acknowledgements are read after, so settling several running statements costs one round trip, not one each.

cancelActiveResponse :: Connection -> ActiveResponse -> IO () Source #

Cancel one response and leave the others alone: finishSession for its session, if the response is still that session's (read part-way, or canceled by an interrupted read and not yet drained); nothing if it has ended, was canceled and drained already, or the connection is closed or broken. A stream of the response throws ResultAbandoned if read on.

readingToEnd :: Connection -> ActiveResponse -> IO a -> IO a Source #

Run an action that reads the response to its end, on a plain or a MARS connection alike. If it throws part-way, the response is not left for the next request to find: a synchronous exception cancels and drains it before it propagates (cancelActiveResponse); an asynchronous one abandons it (abandonResponse), which cancels the statement at once and leaves the drain to the next request (or transaction function, release or cancelActiveStatement), so that an interruption never waits for the server. (An interruption needn't land in a read: it can come just after the request was sent, or between two reads.)

abandonResponse :: Connection -> ActiveResponse -> IO () Source #

After an asynchronous exception out of a function or scope that was reading the response (readingToEnd, withQueryS): mark the response abandoned, so that the next request (or transaction function, release or cancelActiveStatement) drains it; abandon the handle (a new generation), so that a stream read on through it throws ResultAbandoned instead of ending at the acknowledgement, silently missing rows; and cancel the statement at once (cancelStatement). Nothing if the response has ended or was superseded.

Runs masked (from a handler). It never waits for the statement; it may wait for the connection mutex (taken directly: the handle may be stale, and the response is to be marked all the same) and, on a MARS connection, for a slot in the server's window. The abandoned flag is written before the mutex is taken, so that a second interruption in that wait still leaves the response for the next drain to find (on a superseded response, which nothing consults, the flag is harmless); the other state changes come before the send, since they can't fail.

drainCancelled :: Connection -> IO () Source #

Drain every response that was canceled (an Attention sent, by an interrupted read) and not yet read to the acknowledgement, or abandoned by the function that was reading it (readingToEnd). The caller holds the lock.

busyStatements :: Connection -> IO [Text] Source #

The SQL text of each response on the connection that has not been read to its end or canceled, where known. The caller holds the lock.

drainActiveResponse :: Connection -> ActiveResponse -> IO () Source #

Read and discard the rest of the current response, a batch of rows at a time (nothing is decoded). The record given is the session's current one (read from sessResponse, as finishSession does), never a caller's handle: a handle abandoned by an interruption has an older generation, which stepResponse would refuse. (Hmssql.BatchQuery drains with its reader's record on the synchronous path only, where no generation has changed.)

readWholeMessage :: Connection -> IO (PacketType, ByteString) Source #

Read every packet of one message and return the concatenated body.

readWholeMessageUpTo :: Connection -> Int -> IO (PacketType, ByteString) Source #

readWholeMessage, failing with a ProtocolError once the body is longer than the given number of bytes, so a server can't make the client buffer without bound. Used for PRELOGIN, whose response is not a token stream.