hmssql
Safe HaskellNone
LanguageHaskell2010

Hmssql.BatchQuery

Description

Several statements in one round trip, each with its own result.

A BatchQuery pairs each statement with what to do with its results, and runBatchQuery sends them all in one request and reads the results in order:

(updated, Only total, items) <- runBatchQuery conn $ (,,)
  <$> batchExecute ("UPDATE item SET price = price * 1.1 WHERE category = " <> param cat)
  <*> batchQuery1 ("SELECT SUM(price) FROM item WHERE category = " <> param cat)
  <*> batchQuery ("SELECT id, name FROM item WHERE category = " <> param cat)
-- updated :: Int64, total :: Double, items :: [(Int32, Text)]

Each statement is its own sp_executesql call, all of them in one RPC message (as executeStatements sends them): the server compiles and runs them in order, and the response carries each one's results between its own DONEPROC markers, so a statement's consumer sees exactly its own result sets and counts, whatever the statements before it returned. A statement with no parameters is an sp_executesql call too, not a SQL batch, so a SET option it sets or a #temp table it creates lasts only until it returns; statements that must share such state go in one Query (its text joined, as Query is a Monoid) to queryResultSets instead. executeStatements conn qs is runBatchQuery conn (traverse batchExecute qs) with the counting done in bulk, and executeStatementsOutcomes the way to see which statements failed rather than the first that did.

The consumers mirror Hmssql.Query: batchExecute for execute, batchQuery for query (with batchQuery1, batchQueryMay, batchQueryFold, batchQueryS and the With variants), batchQueryMulti for queryMulti, batchQueryResultSets for queryResultSets. A statement's results are read as its consumer asks for them, and what it leaves unread is skipped before the next statement's, so a BatchQuery holds no more memory than its results; batchQueryS streams the last statement's rows after runBatchQuery returns, as queryS does.

Errors

The server runs the statements in order, and __statements after a failed one may have run__: an error that only ends its statement (a constraint violation, say) lets the server go on to the next; an error that aborts the batch (THROW, a conversion error, a deadlock, any error under SET XACT_ABORT ON) ends the request there. Outside a transaction each statement is committed as it completes. The first error is thrown as SqlServerError with its statement's text, once the whole response (and the messages after it, for a batch over about 1 MiB, as executeStatements explains) has been read, so the connection stays usable. For all-or-nothing, run the batch inside withTransaction and let the exception roll it back.

A statement that can't be sent (a value that can't be encoded, a placeholder with no parameter) throws EncodeError, naming its position, before its message is sent; so does a batchQueryS that isn't the last statement.

The shape is hpgsql's Hpgsql.Pipeline. It is called a batch because that is what TDS has: several statements in one request, run in order, each committed on its own, later ones running after an earlier one's error.

Only the last statement may stream. batchQueryS gives a stream to read after runBatchQuery returns, so a stream in the middle would leave the statements after it as actions you would have to run later, in order, on one thread. With only the last one streaming, there is no such order to get wrong.

Synopsis

Documentation

data BatchQuery a Source #

Several statements sent in one request, each paired with what to do with its results: see Hmssql.BatchQuery.

An Applicative, not a Monad: every statement is known before the batch is sent, so a later statement can't depend on an earlier one's rows (the ResultSets monad of one statement can, within it).

Instances

Instances details
Applicative BatchQuery Source # 
Instance details

Defined in Hmssql.BatchQuery

Methods

pure :: a -> BatchQuery a #

(<*>) :: BatchQuery (a -> b) -> BatchQuery a -> BatchQuery b #

liftA2 :: (a -> b -> c) -> BatchQuery a -> BatchQuery b -> BatchQuery c #

(*>) :: BatchQuery a -> BatchQuery b -> BatchQuery b #

(<*) :: BatchQuery a -> BatchQuery b -> BatchQuery a #

Functor BatchQuery Source # 
Instance details

Defined in Hmssql.BatchQuery

Methods

fmap :: (a -> b) -> BatchQuery a -> BatchQuery b #

(<$) :: a -> BatchQuery b -> BatchQuery a #

runBatchQuery :: Connection -> BatchQuery a -> IO a Source #

Run the batch: send the statements, read each one's results as its consumer asks, and read the response to its end. Nothing is sent for a batch of no statements. Statements that don't fit one message go in the next, sent once the previous response has been read; a statement's consumer runs when its results are reached.

Statements

batchExecute :: Query -> BatchQuery Int64 Source #

The statement's rows affected: what execute returns for it.

batchExecute_ :: Query -> BatchQuery () Source #

batchExecute without the count.

Queries

batchQuery :: FromSqlRow r => Query -> BatchQuery [r] Source #

The rows of the statement's first result set: what query returns for it ([] for a statement with no result set).

batchQueryWith :: RowDecoder r -> Query -> BatchQuery [r] Source #

batchQuery with the given row decoder, as queryWith.

batchQuery1 :: FromSqlRow r => Query -> BatchQuery r Source #

Exactly one row, as query1.

batchQuery1With :: RowDecoder r -> Query -> BatchQuery r Source #

batchQuery1 with the given row decoder, as query1With.

batchQueryMay :: FromSqlRow r => Query -> BatchQuery (Maybe r) Source #

Zero or one row, as queryMay.

batchQueryMayWith :: RowDecoder r -> Query -> BatchQuery (Maybe r) Source #

batchQueryMay with the given row decoder, as queryMayWith.

Folding queries

batchQueryFold :: FromSqlRow r => Query -> a -> (a -> r -> IO a) -> BatchQuery a Source #

Fold over the rows of the statement's first result set as they arrive, as queryFold: the step runs with the connection lock held, while the batch is being read, so it can't use the connection.

batchQueryFoldWith :: RowDecoder r -> Query -> a -> (a -> r -> IO a) -> BatchQuery a Source #

batchQueryFold with the given row decoder, as queryFoldWith.

Streaming queries

batchQueryS :: FromSqlRow r => Query -> BatchQuery (Stream (Of r) IO ()) Source #

Stream the rows of the statement's first result set, as queryS: they are read from the socket as the stream is consumed, after runBatchQuery has returned. So the statement must be the last of its batch (runBatchQuery throws EncodeError otherwise, before sending anything), and its stream must be consumed before the next request on the connection, as any queryS stream must (on a MARS connection it stays open beside later requests instead, as a queryS stream does). An error the statement reports is thrown by the stream; one an earlier statement reports, by runBatchQuery.

batchQuerySWith :: RowDecoder r -> Query -> BatchQuery (Stream (Of r) IO ()) Source #

batchQueryS with the given row decoder, as querySWith.

Multiple result sets

batchQueryMulti :: Query -> BatchQuery [ResultSet SqlRow] Source #

Every result set of the statement, as queryMulti.

batchQueryResultSets :: ResultSets a -> Query -> BatchQuery a Source #

The statement's result sets decoded one at a time by a ResultSets action, as queryResultSets: the result sets are the statement's own, and asking for one past its last throws UnexpectedResult.