| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Hmssql.Query
Description
Building queries, running them and reading their results.
Every function takes a Connection and a Query: SQL text with its
parameters, built as described under Hmssql.Query. A query
with no parameters is sent as a plain SQL batch; one with parameters is
sent via sp_executesql, with the positional parameters as @p1,
@p2, ... and the named ones by name.
The functions differ in what they make of the response:
execute: the number of rows affected;execute_discards the count.query: the rows of the first result set, as a list;query1andqueryMayfor exactly one and at most one row.queryS: the rows of the first result set as aStream, read from the socket as they are consumed;queryFoldfolds over them as they arrive.queryCells: each row as a stream of cells, and each cell as a stream of bytes, for a value too large to hold in memory.queryResultSets: several result sets, each into its own row type;queryMulti: every result set, asSqlRows.callProcedure: a stored procedure by RPC, with its output parameters and return status.
The plain functions decode with the row type's FromSqlRow instance;
the With variants take a RowDecoder, and the MWith variants a
RowDecoderMonadic, which runs per row and can look at the columns.
Both forms must consume the whole row. Monadic decoders cannot check
an empty result set, since no decoder runs for it.
A queryS stream must be consumed before another request is made on
the same connection; a newer request cancels the pending one, and the
old stream then throws ResultAbandoned (see queryS). On a MARS
connection (connectMars) it needn't be: several
responses can be open at once, and withQueryS gives a stream that is
canceled when its scope ends.
Several statements in one round trip, each with its own result, are Hmssql.BatchQuery; many rows into one table, Hmssql.BulkCopy.
Synopsis
- data Query
- mkQuery :: ToSqlRow p => Text -> p -> Query
- rawSql :: Text -> Query
- param :: ToSqlField a => a -> Query
- namedParam :: ToSqlField a => Text -> a -> Query
- escapeIdentifier :: Text -> Query
- queryText :: Query -> Text
- execute :: Connection -> Query -> IO Int64
- execute_ :: Connection -> Query -> IO ()
- query :: FromSqlRow r => Connection -> Query -> IO [r]
- query1 :: FromSqlRow r => Connection -> Query -> IO r
- query1With :: RowDecoder r -> Connection -> Query -> IO r
- queryMay :: FromSqlRow r => Connection -> Query -> IO (Maybe r)
- queryMayWith :: RowDecoder r -> Connection -> Query -> IO (Maybe r)
- queryWith :: RowDecoder r -> Connection -> Query -> IO [r]
- queryMWith :: RowDecoderMonadic r -> Connection -> Query -> IO [r]
- queryFold :: FromSqlRow r => Connection -> Query -> a -> (a -> r -> IO a) -> IO a
- queryFoldWith :: RowDecoder r -> Connection -> Query -> a -> (a -> r -> IO a) -> IO a
- queryS :: FromSqlRow r => Connection -> Query -> IO (Stream (Of r) IO ())
- querySWith :: RowDecoder r -> Connection -> Query -> IO (Stream (Of r) IO ())
- querySMWith :: RowDecoderMonadic r -> Connection -> Query -> IO (Stream (Of r) IO ())
- withQueryS :: FromSqlRow r => Connection -> Query -> (Stream (Of r) IO () -> IO a) -> IO a
- withQuerySWith :: RowDecoder r -> Connection -> Query -> (Stream (Of r) IO () -> IO a) -> IO a
- withQuerySMWith :: RowDecoderMonadic r -> Connection -> Query -> (Stream (Of r) IO () -> IO a) -> IO a
- data Cell r = Cell {
- cellColumn :: !ColumnInfo
- cellContext :: !TdsContext
- cellAllocAhead :: !Int
- cellValue :: !(CellValue r)
- data CellValue r
- type RowCells = Stream Cell IO
- queryCells :: Connection -> Query -> IO ([ColumnInfo], Stream RowCells IO ())
- withQueryCells :: Connection -> Query -> ([ColumnInfo] -> Stream RowCells IO () -> IO a) -> IO a
- cellDecoded :: FieldDecoder a -> Cell r -> IO (Either DecodeError a, r)
- cellText :: Cell r -> Stream (Of Text) IO r
- skipCell :: Cell r -> IO r
- data ResultSets a
- resultSet :: FromSqlRow r => ResultSets [r]
- resultSetWith :: RowDecoder r -> ResultSets [r]
- resultSetMWith :: RowDecoderMonadic r -> ResultSets (ResultSet r)
- skipResultSet :: ResultSets ()
- queryResultSets :: ResultSets a -> Connection -> Query -> IO a
- data ResultSet r = ResultSet {
- resultColumns :: ![ColumnInfo]
- resultRows :: ![r]
- queryMulti :: Connection -> Query -> IO [ResultSet SqlRow]
- queryMultiWith :: RowDecoder r -> Connection -> Query -> IO [ResultSet r]
- queryMultiMWith :: RowDecoderMonadic r -> Connection -> Query -> IO [ResultSet r]
- callProcedure :: Connection -> Text -> [ProcParam] -> IO ProcResult
- data ProcParam = ProcParam {}
- inParam :: ToSqlField a => Text -> a -> ProcParam
- outParam :: ToSqlField a => Text -> a -> ProcParam
- rawInParam :: Text -> (TypeInfo, RawBytes) -> ProcParam
- rawOutParam :: Text -> (TypeInfo, RawBytes) -> ProcParam
- data ProcResult = ProcResult {
- procReturnStatus :: !(Maybe Int32)
- procOutputValues :: ![ReturnValue]
- procResultSets :: ![ResultSet SqlRow]
- decodeReturnValue :: FromSqlField a => ReturnValue -> Either DecodeError a
- executeMany :: ToSqlRow p => Connection -> Text -> [p] -> IO [Int64]
- executeStatements :: Connection -> [Query] -> IO [Int64]
- data StatementOutcome
- executeManyOutcomes :: ToSqlRow p => Connection -> Text -> [p] -> IO [StatementOutcome]
- executeStatementsOutcomes :: Connection -> [Query] -> IO [StatementOutcome]
- data Request
- bytesBody :: (TdsContext -> ByteString) -> TdsContext -> PacketWriter -> IO ()
- queryRequest :: Connection -> Query -> IO Request
- queryRequestFor :: TdsContext -> Query -> IO Request
- executeStatementsRequestsFor :: TdsContext -> [Query] -> IO [Request]
- batchRequest :: Text -> Request
- executeSqlRequest :: Text -> [(Text, EncodedParam)] -> IO Request
- rpcRequest :: Text -> [RpcParam] -> IO Request
- paramNameProblem :: Text -> Maybe String
- responseS :: Connection -> Request -> IO (Stream (Of Token) IO ())
- data ResponseReader
- startResponse :: Connection -> Request -> IO ResponseReader
- cancelResponse :: ResponseReader -> IO ()
- responseReader :: Connection -> ActiveResponse -> Maybe Text -> IO ResponseReader
- affectedRows :: ResponseReader -> IO Int64
- readStep :: ResponseReader -> RowRead r -> IO (ResponseStep r)
- drainResponse :: ResponseReader -> IO ()
- data RowBatch r
- data ResponseStep r
Building queries
The simplest query is a string literal (with OverloadedStrings),
sent as a plain batch:
execute conn "CREATE TABLE #t (i int)"
Parameters travel with the text, never inside it. Either write the
SQL in the sp_executesql style, referring to the parameters of a
ToSqlRow value as @p1, @p2, ... (or by name, with
NamedParams):
query conn (mkQuery "SELECT name FROM sys.tables WHERE schema_id = @p1" (Only (1 :: Int)))
or compose the query from pieces, each carrying its own parameters,
with <>:
query conn ("SELECT name FROM sys.tables WHERE schema_id = " <> param (1 :: Int)
<> " AND name LIKE " <> param ("t%" :: Text))The two styles mix freely; placeholders are renumbered as queries
are concatenated, so a query built from parts refers to its
parameters correctly however it was assembled. Identifiers that
come from data go through escapeIdentifier; rawSql splices text
as it is, for SQL assembled at runtime.
A query with no parameters is sent as a SQL batch (statements such
as CREATE PROCEDURE and SET options that must outlive the
request need that); a query with parameters is sent as an
sp_executesql call, whose body runs in its own scope.
SQL text together with the parameters it refers to. Build one from a
string literal, with mkQuery, or from pieces joined with
<>; Hmssql.Query shows each way.
The positional parameters are sent as @p1, @p2, ... in the order
queryParams lists them, and the named parameters by name. <>
appends the text and the parameters of two queries and renumbers the
right operand's placeholders past the left operand's positional
parameters, so any two queries can be joined. A placeholder that
refers to no parameter makes sending the query throw
EncodeError.
A query with no parameters is sent as a plain SQL batch, and one with
parameters through sp_executesql. fromString (OverloadedStrings)
is text as it is, with no parameters. Show is the text as it would
be sent, without the parameter values.
mkQuery :: ToSqlRow p => Text -> p -> Query Source #
SQL text with its parameters. The parameters are those of the
ToSqlRow value: a tuple, a record with a derived instance, an
Only for a single parameter, a list of SqlParam, or
NamedParams. In the text, @p1, @p2, ... refer to the positional
parameters and @name to the named ones.
mkQuery "UPDATE dbo.customer SET email = @p1 WHERE id = @p2" (email, customerId)
@pN, in either case, is a placeholder wherever it appears outside a
string literal, a quoted identifier or a comment, even where the SQL
means something else by it. So a variable of that name, as in
DECLARE @p1 int, can't be used in a mkQuery text; use another name.
A placeholder that refers to no parameter makes sending the query throw
EncodeError.
A string literal (OverloadedStrings) is the same as
for text without placeholders.mkQuery text ()
The text is lexed once per application of mkQuery to it, not once per
set of parameter values. With a literal, the lexing floats out to the
top level when mkQuery is inlined, so the text is lexed once per
program run however many times the query is built with new parameters.
rawSql :: Text -> Query Source #
Text spliced into the query as it is, with no parameters; a string
literal gives the same. Nothing in it is parsed, so a @p1 in it is
left to the server. For text that comes from data, use param,
namedParam or escapeIdentifier instead.
param :: ToSqlField a => a -> Query Source #
A positional parameter: a placeholder in the text, and the value sent with the query.
"WHERE id = " <> param (42 :: Int)
namedParam :: ToSqlField a => Text -> a -> Query Source #
A named parameter: @name in the text, and the value sent with the
query. The leading @ is optional. The name must be a T-SQL variable
name (see paramNameProblem), and not of the form @pN, which is
reserved for the positional parameters. A query that uses one name for
two parameters can't be sent, and throws EncodeError.
"OFFSET " <> namedParam "skip" (100 :: Int) <> " ROWS"
escapeIdentifier :: Text -> Query Source #
An identifier (a table or column name from data, say) quoted with
brackets: [name], with ] doubled.
"SELECT " <> escapeIdentifier col <> " FROM " <> escapeIdentifier table
queryText :: Query -> Text Source #
The SQL text as it is sent, with @pN for the placeholders.
A placeholder next to a character that could continue a variable name
gets a space between them: "x = " <> param 1 <> "1" is sent as
x = @p1 1, not x = @p11 (another parameter), and "N" <> param s
as N @p1, not the identifier N@p1.
Statements
execute :: Connection -> Query -> IO Int64 Source #
Run a statement, or a batch of them, and return the number of rows affected, which is the sum of the counts the server reports. Any result sets are read and discarded.
execute_ :: Connection -> Query -> IO () Source #
Run a statement as execute does, and discard the row count. The
whole response is still read, and a server error is still thrown.
Queries
query :: FromSqlRow r => Connection -> Query -> IO [r] Source #
The rows of the first result set, decoded into the row type by its
FromSqlRow instance, with the column types checked before the first
row. A query with no result set, such as an UPDATE, gives []. The
rest of the response is read to its end, so a later result set is
discarded, and an error from a later statement is still thrown.
query1 :: FromSqlRow r => Connection -> Query -> IO r Source #
query for exactly one row. It throws UnexpectedResult if the
first result set has no rows or several.
query1With :: RowDecoder r -> Connection -> Query -> IO r Source #
query1 with the given row decoder instead of the row type's
FromSqlRow instance. Throws UnexpectedResult unless the first
result set has exactly one row.
queryMay :: FromSqlRow r => Connection -> Query -> IO (Maybe r) Source #
query for at most one row. It returns Nothing if the first result
set has no rows, and throws UnexpectedResult if it has several.
queryMayWith :: RowDecoder r -> Connection -> Query -> IO (Maybe r) Source #
queryMay with the given row decoder instead of the row type's
FromSqlRow instance. Throws UnexpectedResult if the first result
set has more than one row.
queryWith :: RowDecoder r -> Connection -> Query -> IO [r] Source #
query with the given row decoder instead of the row type's
FromSqlRow instance.
queryMWith :: RowDecoderMonadic r -> Connection -> Query -> IO [r] Source #
query with a RowDecoderMonadic instead of a RowDecoder: the decoder
runs per row, so it can look at the columns (remainingColumns) and
decode a shape only known at runtime.
Folding queries
queryFold :: FromSqlRow r => Connection -> Query -> a -> (a -> r -> IO a) -> IO a Source #
Fold over the rows of the first result set as they arrive, without
building a list or a stream: the fastest way to count, sum or write
rows out. The step runs with the connection lock held, in the middle
of reading the response, so it must not use the connection. On a MARS
connection it may, because the request takes a session of its own.
An exception from the step cancels the response at once and
propagates. If it is the step's own exception, the response is
drained before it propagates; if it is an interruption, the drain is
left to the next request. After the fold, the rest of the response is
read to its end, as with query.
queryFoldWith :: RowDecoder r -> Connection -> Query -> a -> (a -> r -> IO a) -> IO a Source #
queryFold with the given row decoder instead of the row type's
FromSqlRow instance.
Streaming queries
queryS and withQueryS give the rows as a Stream from the streaming
package. Rows are read from the socket as you pull them, so memory doesn't grow
with the result:
import qualified Streaming.Prelude as S withQueryS conn "SELECT id, name FROM dbo.customer" $ \rows -> S.mapM_ (\(i :: Int64, name :: Text) -> T.putStrLn (T.pack (show i) <> " " <> name)) rows
Each row is still read whole. To stream a large value within a row, see Hmssql.Decoding.
queryS :: FromSqlRow r => Connection -> Query -> IO (Stream (Of r) IO ()) Source #
The rows of the first result set as a Stream, read from the socket
as they are consumed, so memory doesn't grow with the result.
Hmssql.Query shows how to consume one.
Read the stream to its end before making another request on the
connection. The next request cancels the statement and discards what
was left, and the old stream then throws ResultAbandoned. So does a
stream whose read was interrupted, by timeout say,
which cancels the statement at once, and a stream run a second time
after it was drained.
A stream not read to its end leaves its statement running on the
server, holding its locks, until the next request,
cancelActiveStatement or
release cancels it. On a MARS connection
(connectMars) other requests leave the stream open
instead, so it stays open until it is read to its end, canceled with
cancelActiveStatement, or the connection is
released. withQueryS is the form that can't leak a stream.
Canceling a statement that is still running inside a transaction under
SET XACT_ABORT ON rolls the transaction back
(TransactionEndedEarly). Where the transaction
must survive, read the stream to its end.
querySWith :: RowDecoder r -> Connection -> Query -> IO (Stream (Of r) IO ()) Source #
queryS with the given row decoder instead of the row type's
FromSqlRow instance.
querySMWith :: RowDecoderMonadic r -> Connection -> Query -> IO (Stream (Of r) IO ()) Source #
queryS with a RowDecoderMonadic instead of a RowDecoder: the decoder
runs per row, so it can look at the columns (remainingColumns) and
decode a shape only known at runtime.
withQueryS :: FromSqlRow r => Connection -> Query -> (Stream (Of r) IO () -> IO a) -> IO a Source #
queryS in a scope. The action gets the stream, and when the action
ends, a stream not read to its end is canceled (cancelResponse).
When the action returns, or throws an exception of its own,
withQueryS returns once the statement is over on the server and its
locks are released. On a MARS connection
(connectMars) this is the way to read a stream
beside other requests. Without MARS it works too, under the rule of
queryS: a request the action makes abandons the stream.
withQueryS conn "SELECT id FROM dbo.parent" $ \parents ->
S.mapM_ (\(Only pid) -> query conn ("SELECT ... WHERE parent_id = " <> param (pid :: Int))) parentsAn interruption (timeout, killThread)
doesn't wait. The statement is canceled at once, and the connection's
next request reads the rest of the response first; a transaction
function, release and
cancelActiveStatement do the same. Until then the
statement's locks may still be held, and
cancelActiveStatement waits for them to go. The
stream must not be used after the action.
withQuerySWith :: RowDecoder r -> Connection -> Query -> (Stream (Of r) IO () -> IO a) -> IO a Source #
withQueryS with the given row decoder instead of the row type's
FromSqlRow instance.
withQuerySMWith :: RowDecoderMonadic r -> Connection -> Query -> (Stream (Of r) IO () -> IO a) -> IO a Source #
withQueryS with a RowDecoderMonadic instead of a RowDecoder, as
querySMWith.
Streaming cells
queryCells and withQueryCells give nested streams: rows of cells, and cells
of bytes. They let what you read in one column decide what to do with a later
one. Here, each row's first column names the file for its second, using toFile
from Hmssql.Decoding:
import Streaming (inspect)
withQueryCells conn "SELECT name, payload FROM dbo.documents" $ \_ rows ->
let nextRow remaining = inspect remaining >>= \case
Left () -> pure ()
Right row -> do
Right nameCell <- inspect row
(Right name, afterName) <- cellDecoded (fieldDecoder @FilePath) nameCell
Right payloadCell <- inspect afterName
(Right (), afterPayload) <- cellDecoded (streamField (toFile name)) payloadCell
Left more <- inspect afterPayload
nextRow more
in nextRow rowsWhen each column can be handled without the others, a field decoder is simpler and faster (Hmssql.Decoding).
One column's value in a row being read, followed by r, the rest of the
row. skipCell, cellText and cellDecoded are the usual ways to read one.
Constructors
| Cell | |
Fields
| |
A cell's value and what follows it. A NULL gives the rest of the row at once. A non-NULL value has its total length, if known, and its bytes, and gives the rest of the row when they end, so a row's cells can only be read in order.
type RowCells = Stream Cell IO Source #
One row's cells, in column order. Its result is the stream of the rows after it.
queryCells :: Connection -> Query -> IO ([ColumnInfo], Stream RowCells IO ()) Source #
The columns of the first result set, and its rows as nested streams: rows of
cells, and cells of bytes. A value is read as it arrives, so a large one
needn't be in memory. For ordinary rows, queryS is faster.
The rules of queryS apply: a new request abandons the stream on a plain
connection, and on MARS it doesn't. Reading any part of the stream a second
time, or using it after its scope ends, throws ResultAbandoned.
withQueryCells :: Connection -> Query -> ([ColumnInfo] -> Stream RowCells IO () -> IO a) -> IO a Source #
queryCells in a scope: when the action ends, a stream not read to its end is
canceled, with the same interruption rules as withQueryS.
cellDecoded :: FieldDecoder a -> Cell r -> IO (Either DecodeError a, r) Source #
Decode a cell with any field decoder, and get the rest of the row. A failed
conversion is a Left once the value has been read past, so the row can go on.
cellText :: Cell r -> Stream (Of Text) IO r Source #
A character or XML cell's value as Text chunks, read as it arrives, returning
the rest of the row. A NULL gives no chunks. A column whose encoding can't be
decoded throws DecodeError.
Several result sets
data ResultSets a Source #
How to decode a response that holds several result sets, one at a
time in order, for queryResultSets. Build one from
resultSet and skipResultSet with the
Applicative or Monad combinators; as a Monad, a later decoder may
depend on earlier rows. Statements that produce no result set, such as
DECLARE, SET and UPDATE, are skipped over.
(parents, children) <- queryResultSets ((,) <$> resultSet <*> resultSet) conn "SELECT id, name FROM dbo.parent; SELECT id, parent_id FROM dbo.child"
Instances
| Applicative ResultSets Source # | |
Defined in Hmssql.Query.Types Methods pure :: a -> ResultSets a # (<*>) :: ResultSets (a -> b) -> ResultSets a -> ResultSets b # liftA2 :: (a -> b -> c) -> ResultSets a -> ResultSets b -> ResultSets c # (*>) :: ResultSets a -> ResultSets b -> ResultSets b # (<*) :: ResultSets a -> ResultSets b -> ResultSets a # | |
| Functor ResultSets Source # | |
Defined in Hmssql.Query.Types Methods fmap :: (a -> b) -> ResultSets a -> ResultSets b # (<$) :: a -> ResultSets b -> ResultSets a # | |
| Monad ResultSets Source # | |
Defined in Hmssql.Query.Types Methods (>>=) :: ResultSets a -> (a -> ResultSets b) -> ResultSets b # (>>) :: ResultSets a -> ResultSets b -> ResultSets b # return :: a -> ResultSets a # | |
resultSet :: FromSqlRow r => ResultSets [r] Source #
The rows of the next result set, as query would return them. It
throws UnexpectedResult if the response has no further result set.
resultSetWith :: RowDecoder r -> ResultSets [r] Source #
resultSet with the given row decoder instead of the row type's
FromSqlRow instance.
resultSetMWith :: RowDecoderMonadic r -> ResultSets (ResultSet r) Source #
resultSet with a RowDecoderMonadic, which runs per row and can look at
the columns (remainingColumns), and the result set's columns returned
with its rows.
skipResultSet :: ResultSets () Source #
Read the next result set and discard its rows. It throws
UnexpectedResult if the response has no further result set.
queryResultSets :: ResultSets a -> Connection -> Query -> IO a Source #
Run a batch that returns several result sets, and decode them one at a time, each into its own row type.
(parents, children) <- queryResultSets ((,) <$> resultSet <*> resultSet) conn "SELECT id, name FROM dbo.parent; SELECT id, parent_id FROM dbo.child"
The whole response is read, so an error in a later statement still
throws SqlServerError, and result sets the ResultSets didn't ask
for are discarded.
One result set of a response: its columns and its rows.
Constructors
| ResultSet | |
Fields
| |
queryMulti :: Connection -> Query -> IO [ResultSet SqlRow] Source #
Every result set in the response, in order, each row as a SqlRow
(sqlRowDecoderFor the set's columns): for a batch or procedure whose
columns are only known at runtime. For result sets of known types, use
queryResultSets.
queryMultiWith :: RowDecoder r -> Connection -> Query -> IO [ResultSet r] Source #
Every result set decoded with the given row decoder. Each set's
column count and types are checked before reading its rows, including
empty sets. Use queryResultSets when the sets have different known shapes.
queryMultiMWith :: RowDecoderMonadic r -> Connection -> Query -> IO [ResultSet r] Source #
queryMulti with a RowDecoderMonadic instead of SqlRow: every result
set in the response, each decoded with the same decoder, which runs per
row and can look at the columns (remainingColumns) to adapt to each
set.
Stored procedures
callProcedure :: Connection -> Text -> [ProcParam] -> IO ProcResult Source #
Call a stored procedure (or scalar function, or system procedure such as
sp_executesql) by RPC, with the parameters in the given order. See
ProcResult for how output values are ordered. Like the other query
functions, it throws SqlServerError if the server reports an error, even
one the procedure continues past; output values are then not available.
Parameters are encoded with the connection's context and validated
before sending; an encoding failure throws EncodeError.
res <- callProcedure conn "dbo.place_order" [ inParam "@customer_id" (42 :: Int32) , outParam "@order_id" (Nothing :: Maybe Int32) ] let [orderId] = procOutputValues res print (decodeReturnValue orderId :: Either DecodeError (Maybe Int32), procReturnStatus res)
A procedure with typed result sets and no output parameters can also be
run as EXEC dbo.report @p1 through queryResultSets.
A parameter of a callProcedure call. inParam and outParam build
one from a ToSqlField value; rawInParam and rawOutParam declare an
exact wire type. Encoding runs in callProcedure, with the connection's
context.
Constructors
| ProcParam | |
Fields
| |
inParam :: ToSqlField a => Text -> a -> ProcParam Source #
An input parameter, encoded with the connection's context when
callProcedure runs. Encoding failures throw EncodeError before
the procedure request is sent.
inParam "@customer_id" (42 :: Int32)
outParam :: ToSqlField a => Text -> a -> ProcParam Source #
An input/output parameter; the value is its initial value (a NULL of
the right type, Nothing :: Maybe Int32, when it has none). Its value
after the call is in procOutputValues, decoded with
decodeReturnValue.
The server returns an output value converted to the type declared for the parameter, which is derived from the initial value, so that type is widened so the output isn't truncated:
- strings and binary (
char,varchar,nchar,nvarchar,binary,varbinary) are declared as(max); decimalandnumericare declared with precision 38 and scale 10, or the initial value's scale if that is larger (but less than 10 if the initial value's integer digits need it). So0 :: DecimalandNothing :: Maybe Decimalboth declaredecimal(38,10); to get more decimal places give the initial value a larger scale (e.g.).roundTo20 0
Other types are declared as their ToSqlField instance sends them. An
xml output parameter needs an xml initial value such as
(the server won't convert xml to the SqlXml ""nvarchar a Text
initial value declares). For full control of the declared type, use
rawOutParam, which widens nothing.
Encoding and widening run in callProcedure, with the connection's
context; failures throw EncodeError before the procedure request is sent.
rawInParam :: Text -> (TypeInfo, RawBytes) -> ProcParam Source #
An input parameter with an exact wire type and encoded value, for a
type no ToSqlField instance declares:
rawInParam "@x" (TIDecimalN 20 4, decimalValue)
callProcedure still validates the wire type and the value before
sending.
rawOutParam :: Text -> (TypeInfo, RawBytes) -> ProcParam Source #
rawInParam as an input/output parameter: the declared type is the
one given, with none of the widening of outParam, so the server converts
the output value to exactly it:
rawOutParam "@x" (TIDecimalN 20 4, Nothing) -- a NULL decimal(20,4)
data ProcResult Source #
What callProcedure returns: the return status, the output
parameters' values and the result sets.
Constructors
| ProcResult | |
Fields
| |
Instances
| Show ProcResult Source # | |
Defined in Hmssql.Query Methods showsPrec :: Int -> ProcResult -> ShowS # show :: ProcResult -> String # showList :: [ProcResult] -> ShowS # | |
decodeReturnValue :: FromSqlField a => ReturnValue -> Either DecodeError a Source #
Decode an output parameter's value (procOutputValues) as a column
of its declared type would be decoded.
Several statements in one request
executeMany :: ToSqlRow p => Connection -> Text -> [p] -> IO [Int64] Source #
Run one statement text once for each row of parameters, all in one
request, and return the number of rows each affected, in order. The
text refers to a row's parameters as a mkQuery text does, with
@p1, @p2, ..., and a row is any ToSqlRow value.
executeMany conn "INSERT INTO item (id, name) VALUES (@p1, @p2)" [(1 :: Int, "one" :: Text), (2, "two")]
Each row is its own sp_executesql call, with the text and the
parameter declaration shared (read and encoded once, not per row), so
the server compiles the statement once and reuses the plan. It is
with that
sharing, and everything executeStatements conn (map (mkQuery sql) rows)executeStatements says about round trips,
errors, transactions and interruption applies: statements after a
failed row may have run, so use withTransaction
for all or nothing, or executeManyOutcomes to see which rows failed.
For a large load into one table, bulkInsert is
faster.
executeStatements :: Connection -> [Query] -> IO [Int64] Source #
Run several statements in one request, which is one round trip, and
return the number of rows each affected, in order, as execute would
return it. Each statement is sent as its own sp_executesql call, all
in one RPC message. That includes a statement with no parameters, so a
SET option it sets or a #temp table it creates lasts only until it
returns; send such statements with execute. For many rows of one
statement, executeMany is the same with the text read once. To know
which statements failed, use executeStatementsOutcomes.
The server runs the statements in order. If some fail, the whole
response is still read, and then SqlServerError is thrown for the
first that failed. Statements after a failed one may have run: an
error that only ends its statement (a constraint violation, say) lets
the server go on; one that aborts the batch (THROW, a conversion
error, a deadlock, any error under SET XACT_ABORT ON) ends the
request there. For all-or-nothing, run it inside
withTransaction.
A statement that can't be sent (see queryRequest) throws
EncodeError naming its position, before its message is sent. A long
list goes out in messages of about 1 MiB, each read before the next is
sent, so memory stays bounded; the split changes nothing else.
Interrupted, it cancels the running statement; the statements before
it have run. An empty list returns [] without a round trip.
data StatementOutcome Source #
What happened to one statement of executeStatementsOutcomes or
executeManyOutcomes.
Constructors
| StatementDone !Int64 | It ran, and affected this many rows (what |
| StatementFailed !ServerMessage | It failed with this error (the first, if it reported several). |
| StatementNotRun | The request ended before it ran: a statement before it aborted
the batch (with |
Instances
| Show StatementOutcome Source # | |
Defined in Hmssql.Query Methods showsPrec :: Int -> StatementOutcome -> ShowS # show :: StatementOutcome -> String # showList :: [StatementOutcome] -> ShowS # | |
| Eq StatementOutcome Source # | |
Defined in Hmssql.Query Methods (==) :: StatementOutcome -> StatementOutcome -> Bool # (/=) :: StatementOutcome -> StatementOutcome -> Bool # | |
executeManyOutcomes :: ToSqlRow p => Connection -> Text -> [p] -> IO [StatementOutcome] Source #
executeMany with each row's outcome, as executeStatementsOutcomes: the
way to tell which rows failed.
outcomes <- executeManyOutcomes conn "INSERT INTO item (id, name) VALUES (@p1, @p2)" rows let failed = [(row, msgText err) | (row, StatementFailed err) <- zip rows outcomes]
executeStatementsOutcomes :: Connection -> [Query] -> IO [StatementOutcome] Source #
executeStatements, returning what happened to each statement instead of
throwing the first failure: the statements' outcomes, one for each, in
order. A failed statement's error is its StatementFailed; the
statements after one that aborted the batch are StatementNotRun.
Nothing else changes: an EncodeError, a SevereError, an
interruption and a connection that fails are thrown as by
executeStatements, and so is an error that closes the connection (severity
20 or more), as SqlServerError.
Low level
Requests and responses as the query functions see them, for code
that reads a response token by token. Nothing here is needed to
use the functions above. The constructors of Request,
ResponseStep and RowBatch are in Hmssql.Query.Types.
A message to send: its packet type and its body, written for the
connection's context when it is sent. queryRequest
builds one from a Query; startResponse and
responseS send one.
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.
queryRequest :: Connection -> Query -> IO Request Source #
The request a query is sent as: a SQL batch when it has no parameters,
otherwise sp_executesql with the parameters encoded for the connection.
Throws EncodeError for a value that can't be encoded, a placeholder
with no parameter (queryProblem), a name that
isn't a T-SQL variable name (paramNameProblem) or is of the reserved
form @pN, or a name used twice (SQL Server identifiers are
case-insensitive, so @a and @A are one name). Nothing is sent.
queryRequestFor :: TdsContext -> Query -> IO Request Source #
queryRequest for a connection with this context, without the
connection. It throws EncodeError as queryRequest does. A request with
a lazy or streamed parameter can be sent only once.
executeStatementsRequestsFor :: TdsContext -> [Query] -> IO [Request] Source #
The messages executeStatements sends for these statements, for a
connection with this context (as queryRequestFor for one query): each
statement an sp_executesql RPC, in messages of about 1 MiB; or why a
statement can't be sent.
batchRequest :: Text -> Request Source #
A SQL batch: the text as it is, no parameters.
executeSqlRequest :: Text -> [(Text, EncodedParam)] -> IO Request Source #
sp_executesql @stmt, @params, @p1, @p2, ...
Parameters with an empty name are called @pN after their 1-based position
in the list. The names are spliced into the @params declaration as
given, so check them with paramNameProblem first (the query functions
do). With no parameters, @params is left out.
rpcRequest :: Text -> [RpcParam] -> IO Request Source #
An RPC call of the named procedure with these parameters, as
callProcedure sends it. Invalid parameters throw EncodeError before
the request is constructed.
paramNameProblem :: Text -> Maybe String Source #
Why a parameter name can't be used, if it can't: it must be a T-SQL
variable name, @ followed by one or more letters, combining marks,
decimal digits, @, #, $ or _ (Unicode ones included, but only
from the Basic Multilingual Plane: SQL Server rejects surrogates in
identifiers). The query functions' parameter names are spliced into the
sp_executesql declaration, where anything else (a space, a comma, a
parenthesis, a comment) would change what is declared. callProcedure
doesn't use this check: its parameter names are sent as data, and the
server reports the ones it can't match. SQL Server itself accepts a few
more names (a bare @, and ignores format characters such as U+200D
and trailing spaces in RPC parameter names), and rejects some letters
added in recent Unicode versions; it enforces its 128-character limit
itself.
responseS :: Connection -> Request -> IO (Stream (Of Token) IO ()) Source #
Send a request and stream the response tokens, a low-level view of a
response: every row as a TRow (see Hmssql.Tds.Value), and the other
tokens, in order. ERROR tokens are withheld and rethrown as
SqlServerError once the response is complete, so the connection is
never left mid-message. A DONE (DONE, DONEPROC or DONEINPROC) flagged
DONE_ERROR or DONE_SRVERROR in a response with no ERROR token is thrown
as SevereError at the end, likewise: the server failed the request
without a message (as when the session was killed).
data ResponseReader Source #
A response being read: the reader the query functions share, pulling
one step at a time (readStep) until StepEnd. ERROR
tokens are withheld (the first kept) and thrown as
SqlServerError once the response is complete, so the
connection is never left mid-message; a DONE flagged as failed in a
response with no ERROR token is thrown as SevereError
then.
Not a SQL cursor: it reads one response forwards, once.
startResponse :: Connection -> Request -> IO ResponseReader Source #
Send a request and start reading its response.
cancelResponse :: ResponseReader -> IO () Source #
Cancel one response: if it hasn't been read to its end, the
statement is canceled on the server and the rest of the response
discarded, as cancelActiveStatement does for every
response of the connection. It returns once the response is over on
the server, so the statement's locks are released by then (a round
trip, unless the response had fully arrived). Nothing happens if the
response was already read to its end or canceled, or the connection is
closed or broken. Reading on from the reader, or from a stream of it,
throws ResultAbandoned.
responseReader :: Connection -> ActiveResponse -> Maybe Text -> IO ResponseReader Source #
The cursor of a response started another way
(startStreamingRequest), with the
statement's text for its error messages.
affectedRows :: ResponseReader -> IO Int64 Source #
Sum of the row counts reported by DONE tokens, reading the response to its end.
readStep :: ResponseReader -> RowRead r -> IO (ResponseStep r) Source #
The next thing in the response (nextStep), with the rows decoded
by the given function: a batch of rows, a token other than a row or
a nonfatal ERROR (withheld), or StepEnd, which throws the withheld error (or
SevereError) instead if there was one. A response that ends with
the connection closed after an error (by the server, as after a
severity 20 error, or by a failed transport) throws that error; after
close, the ConnectionClosed that every use of the
connection gets. (The withheld error is the reader's, not a stream's:
a stream run again from a step before the error would otherwise report
an error it hadn't seen.)
A fatal ERROR is thrown by the common token reader immediately and leaves the connection broken.
Reading one statement of a batch query (respBatch): the statement's
DONEPROC is returned and then StepEnd, for as long as the statement
stays the current one; an ERROR token isn't withheld but ends the batch
(scopeFail), and the response's end throws SevereError for a
failed DONE but never a withheld error, which there is none of.
drainResponse :: ResponseReader -> IO () Source #
Read the rest of the response and discard it. The withheld error, if any, is thrown at the end. Nothing to do once the end has been read.
The rows at the head of the response, decoded: what one nextStep
returns when it finds rows.
data ResponseStep r Source #
What nextStep found next in the response.