hmssqlguide

Decoders and encoders

Streaming values

Streaming rows reads a result one row at a time. This page is about a single value that is too large to hold in memory: a column consumed in chunks as it arrives, a parameter read from a source as it is sent, and the generic cell interface underneath.

Streaming a column#

streamField consumes a column as its chunks arrive. It works in any column, including several large columns among small ones:

import qualified Data.ByteString as B
import Hmssql.Decoding (singleField, streamField, valueChunks)
import Hmssql.Query (queryWith)

let byteCount = singleField (streamField (S.sum_ . S.map B.length . valueChunks))
sizes <- queryWith byteCount conn "SELECT payload FROM dbo.documents"

The consumer runs on your reading thread. Any bytes it leaves unread are skipped. Its stream expires when it returns, and replaying a pull throws ResultAbandoned.

A value of 64 KiB or less is read whole first and handed over as one chunk, which costs about what fieldDecoder does. Only larger values arrive in pieces, so streamField suits a column of mostly short values too.

An exception from the consumer ends the query at once: the statement is canceled, the exception reaches you as it was thrown, and the connection stays usable. To report a bad value instead, return an Either String and wrap the decoder in refineField id, which gives a DecodeError naming the column.

Use nullableField for a nullable column and streamTextField for text; text decoding handles characters split across packets. A consumer may use other responses on a MARS connection, but it must not use its own plain connection.

A slow consumer holds the connection lock, and may keep transaction locks. Choose its work accordingly.

Sending a large value#

Use a source when a parameter should be read as it is sent:

import Hmssql.Encoding (binaryStream, sourceOf)
import qualified Streaming.Prelude as S

let chunks = S.each ["first", "second"]
execute conn $ "INSERT INTO dbo.documents(payload) VALUES ("
  <> param (binaryStream (sourceOf chunks)) <> ")"

textStream takes Text chunks, and streamAs chooses a PLP wire type for raw bytes. Several streamed parameters can appear among ordinary parameters.

A Source can scope resources such as an open file around the whole send. It runs masked. Starting a request on its own connection throws ReentrantSource; on a MARS connection it may read an earlier response.

A Query can be sent again. An explicit Request containing a lazy value or a source is single-use. Output parameters remain in memory and refuse sources.

Bulk copy accepts streamed cells too, and Bulk copy has the rules.

Generic cells#

queryCells returns the column metadata and nested streams of rows, cells and bytes. Finishing a value gives the next cells, and finishing a row gives the next rows. skipCell, cellText and cellDecoded handle the common cases, and cellDecoded uses the connection context and allocation bound carried by the cell, including for custom decoders.

withQueryCells cancels and drains anything left at scope exit. Each pull holds the lock only for that pull, so on a MARS connection other requests can run between pulls. For a known row shape, the typed field interface above is simpler.