hmssql
Safe HaskellNone
LanguageHaskell2010

Hmssql

Description

hmssql is a native Haskell client for Microsoft SQL Server (2017 and later) and Azure SQL Database. It implements SQL Server's wire protocol directly (TDS 7.4 and 8.0), so no ODBC driver or client library is needed.

The query API resembles postgresql-simple's, with one key difference: a query and its parameters are one Query value, so query conn q params becomes query conn q. Unlike hpgsql (a newer PostgreSQL library that inspired this one), hmssql does not include a quasiquoter.

This module exports what most programs need: connecting, building and running queries, streaming, transactions, and the classes that turn rows into your records and your values into parameters. The rest of the library is a module each, listed at the end of this page.

Getting started

{-# LANGUAGE DeriveAnyClass, DerivingStrategies, OverloadedStrings #-}
import Data.Text (Text)
import Data.Time (Day, fromGregorian)
import GHC.Generics (Generic)
import Hmssql

-- The columns of a SELECT, in order.  A nullable column is a Maybe.
data Customer = Customer
  { customerId :: Int
  , name :: Text
  , birthDate :: Maybe Day
  }
  deriving stock (Show, Generic)
  deriving anyclass (FromSqlRow)

info :: ConnectInfo
info = defaultConnectInfo
  { connectUser = "sa"
  , connectPassword = "..."
  , connectDatabase = "your_database"
  , connectTls = defaultTlsSettings { tlsTrustServerCertificate = True }
  }

main :: IO ()
main = withConnection info $ \conn -> do
  -- A value never goes into the SQL text: param makes it a parameter.
  customers <- query conn $
    "SELECT id, name, birth_date FROM dbo.customer WHERE birth_date < "
      <> param (fromGregorian 1900 1 1)
  mapM_ print (customers :: [Customer])

  -- Or write the SQL with @p1, @p2, ... and pass a tuple or a record.
  n <- execute conn $ mkQuery
    "INSERT INTO dbo.customer (name, birth_date) VALUES (@p1, @p2)"
    ("Ada" :: Text, Just (fromGregorian 1815 12 10))
  putStrLn (show n ++ " row inserted")

The columns of the SELECT match the record's fields by position. Before the first row is decoded, each column's type is checked against its field, and a mismatch throws DecodeError naming the column. A tuple works in place of a record, and Only wraps a single column.

Connections are encrypted, and by default the server's certificate is validated against the system's certificate store. A SQL Server on localhost uses a self-signed certificate it generated for itself, so that validation fails, and tlsTrustServerCertificate skips it. Use it for development only.

Connecting

Start from defaultConnectInfo, which connects to localhost on port 1433, encrypts the connection and validates the server's certificate against the system store, and change the fields that differ. withConnection opens a connection, runs an action with it and closes it afterward. connect and close are the two halves, for a connection that outlives one action.

For Azure SQL Database, set the host name, the database and the credentials, and leave the TLS settings alone: Azure's certificate validates as it is.

azure = defaultConnectInfo
  { connectHost = "myserver.database.windows.net"
  , connectUser = "your_login"
  , connectPassword = "..."
  , connectDatabase = "your_database"
  }

A Connection belongs to one thread at a time, and it runs one request at a time unless it was opened with connectMars. Give each thread its own connection, from a pool. release cleans a connection up as it goes back to the pool, and renew gives the next borrower a fresh handle to it. Hmssql.Connection has a recipe for the resource-pool package.

Building queries

A Query is SQL text together with its parameters. With OverloadedStrings, a string literal is a query with no parameters. A value never goes into the SQL text itself. There are two ways to pass it as a parameter, and they mix:

  • param turns a value into a parameter where it is written, and <> joins queries:
"SELECT id FROM dbo.customer WHERE name = " <> param name
  <> " AND birth_date < " <> param cutoff

Joining renumbers the placeholders, so SQL assembled at runtime from optional pieces always refers to the right parameters. namedParam gives a parameter a name, escapeIdentifier quotes a table or column name that comes from data, and rawSql splices text in as it is.

  • mkQuery takes SQL text written with @p1, @p2, ... and one ToSqlRow value holding the parameters: a tuple, a record with a derived instance, an Only for a single parameter, a list of SqlParam, or NamedParams for text written with @name placeholders.
mkQuery "UPDATE dbo.customer SET email = @p1 WHERE id = @p2" (email, customerId)

queryText renders a query as it is sent, with @p1 in place of each value, for logging.

A query with no parameters is sent as a SQL batch. A query with parameters goes through sp_executesql, whose body runs in a scope of its own, so a SET option or a #temp table it creates is gone when it returns.

Running queries

Every function here takes the connection and a Query. They differ in what they make of the response:

  • execute runs a statement and returns the number of rows it affected. execute_ discards the count.
  • query returns the rows of the first result set as a list.
  • query1 returns exactly one row, and queryMay zero or one. Any other number of rows throws UnexpectedResult.
  • queryS returns the rows as a stream, read from the socket as they are consumed, and withQueryS gives the stream a scope. The next section explains.
  • queryFold folds over the rows as they arrive, with neither a list nor a stream.
  • queryResultSets runs a batch that returns several result sets and decodes each into its own row type. Its ResultSets argument is built from resultSet and skipResultSet.
  • executeMany runs one statement text once for each row of parameters, in one round trip. executeStatements runs a list of statements in one round trip.

Hmssql.Query has the rest: the With variants, which take a decoder of your own instead of the row type's instance; queryMulti, for result sets whose columns are known only at runtime; callProcedure, for stored procedures with output parameters; and the forms of executeMany that report each statement's outcome instead of throwing the first failure. Hmssql.BatchQuery pairs each of several statements with what to do with its result and sends them all in one round trip. Hmssql.BulkCopy loads many rows into one table by the protocol's bulk load, which is the fastest way to fill a table.

Streaming

queryS returns the rows as a Stream from the streaming package instead of as a list. A row is read from the socket when you pull it, so memory doesn't grow with the result. Consume the stream with the functions of Streaming.Prelude, which has the functions a list has, such as S.mapM_, S.fold_ and S.take:

import qualified Streaming.Prelude as S

withQueryS conn "SELECT id, name FROM dbo.customer" $ \rows ->
  S.mapM_ (\(i :: Int, name :: Text) -> print (i, name)) rows

withQueryS runs an action with the stream and cancels whatever the action didn't read, so the stream never outlives its scope. queryS hands you the stream itself, with one rule: read it to its end before the connection's next request. That request cancels the statement, and the old stream then throws ResultAbandoned. A connection opened with connectMars keeps several streams open at once, and Hmssql.Connection has the rules.

Each row is still read whole. To stream one large value, such as a varbinary(max) column that holds a file, see Hmssql.Decoding. Hmssql.Encoding sends a large parameter the same way.

Rows and parameters

FromSqlRow turns a row into a value. Derive it for a record whose fields are FromSqlField types, one field per column of the SELECT in order, as Customer above does. Tuples and Only have instances already. FromSqlField turns one column into a value, and the usual Haskell types have instances: Int, Text, Double, Day, LocalTime, ByteString and so on. A nullable column is a Maybe. Hmssql.Types has the table of which SQL types each accepts.

ToSqlRow and ToSqlField are the other direction, for parameters. Derive ToSqlRow for a record to pass to mkQuery or executeMany:

data NewCustomer = NewCustomer { newName :: Text, newBirthDate :: Maybe Day }
  deriving stock (Generic)
  deriving anyclass (ToSqlRow)

SqlParam wraps any parameter so that a list can mix types, and NamedParams pairs names with such parameters.

To decode a column into a type of your own, write a FromSqlField instance with the combinators of Hmssql.Decoding, usually refineField over an existing decoder, for an enumeration stored as a varchar or for a JSON column, say. Hmssql.Encoding has the same for parameters. Hmssql.SqlValue is a value of any SQL type, for columns and parameters whose types are known only at runtime.

Transactions

withTransaction runs an action in a transaction. It commits if the action returns and rolls back if it throws. withTransactionLevel does the same at a chosen IsolationLevel. A transaction can't be begun inside another, and a transaction the server ended under the action, after a deadlock or an error under XACT_ABORT, is reported as an exception rather than committed. Hmssql.Transaction has those exceptions, and the manual begin, commit and rollback.

Handling errors

Every exception hmssql throws is in Hmssql.Errors. After a SqlServerError, which means the server reported an error, or after a DecodeError, EncodeError or UnexpectedResult, the connection is still usable. After a ConnectionClosed, ConnectionBroken or ProtocolError it is not: close it and open another.

Nothing here takes a timeout argument. Interrupt a call with an asynchronous exception instead, from timeout or killThread. The statement is canceled on the server, and the connection stays usable.

The other modules

Synopsis

Connections

data Connection Source #

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

data ConnectInfo Source #

How to connect. Start from defaultConnectInfo and change the fields that differ. Textual settings are Text, the host, port and instance included.

Azure SQL Database accepts both the default EncryptOn, which is TDS 7.4, and EncryptStrict, which is TDS 8.0. The server name in the login defaults to connectHost. Under Azure's Default connection policy, a connection from inside Azure through the public endpoint is redirected to the node that hosts the database, on a port from 11000 to 11999, and connect follows the redirect, so those outbound ports must be open.

azure = defaultConnectInfo
  { connectHost = "myserver.database.windows.net"
  , connectUser = "your_login"
  , connectPassword = "..."
  , connectDatabase = "your_database"
  }

To log in with Entra ID instead of a SQL login, put an access token in connectAccessToken and leave the user and password empty.

Constructors

ConnectInfo 

Fields

  • connectHost :: !Text

    A host name or an IP address. A name that resolves to several addresses is tried in the resolver's order until one accepts. An address that hasn't answered within 4 seconds is given up and the next is tried; the last one waits as long as the operating system allows. Whatever the protocol, this is also the name the server's certificate is checked against.

  • connectPort :: !Text

    A port number, or a service name. Used over TCP only.

  • connectProtocol :: !Protocol

    What the connection travels on. The default, ProtocolAuto, is TCP everywhere but on Windows, where it does what Microsoft's clients do, so that a SQL Server on the same machine is reached even with TCP/IP turned off, as it is after a default installation of some editions.

  • connectInstance :: !Text

    The name of a named instance, for ProtocolSharedMemory and ProtocolNamedPipes, which find an instance by its name; empty for the default instance. TCP ignores it: a named instance is reached by its port (hmssql doesn't ask the SQL Server Browser).

  • connectUser :: !Text

    The SQL Server login. (Windows authentication is not supported; Entra ID is, through connectAccessToken.)

  • connectPassword :: !Text
     
  • connectAccessToken :: !(Maybe Text)

    An Entra ID access token, used in place of connectUser and connectPassword, which must then be empty, or connect throws EncodeError. Obtaining the token is the caller's job. It is what Azure Managed Identity's endpoint, a service principal's credentials or az account get-access-token return for the audience https://database.windows.net/, passed as it comes: hmssql sends it in the login, as SqlClient's AccessToken does, and never parses it.

    The server checks the token at login only, so a connection outlives its token's expiry. A pool should get a fresh token before each connect; the managed identity endpoint caches them. With EncryptOff or EncryptNotSup the token would travel in plaintext, like a password, but Azure SQL requires encryption anyway.

  • connectDatabase :: !Text

    The database to open in; empty for the login's default.

  • connectEncryption :: !Encryption

    The default, EncryptOn, encrypts the whole session and refuses a server that can't. EncryptStrict selects TDS 8.0, where TLS starts before the first byte of TDS; the other modes negotiate TLS in PRELOGIN, under TDS 7.4.

  • connectTls :: !TlsSettings

    How the server's certificate is validated: against the system store by default. A local server with a self-signed certificate needs tlsTrustServerCertificate, or its certificate in tlsCaCertificateFiles.

  • connectApplicationName :: !Text

    What the session shows as program_name in sys.dm_exec_sessions.

  • connectServerName :: !Text

    The server name sent in the login. Empty, the default, sends connectHost as it is, a fully qualified Azure SQL host name included. Set it when a tunnel or an alias gives the server a different network address from its logical name. A login redirect replaces it with the destination the server names.

  • connectLanguage :: !Text

    The session's language (SET LANGUAGE), which sets the date format and the language of the server's messages; empty for the login's default.

  • connectPacketSize :: !Word32

    The packet size to ask for, header included: 8000 by default, as SqlClient's. Values outside 512 to 32767 are clamped to that range. The server may grant less than asked, and connectionContext has the negotiated size. With connectMars over TLS, at most 16368 is asked for, because a packet and the 16 bytes MARS adds to it must fit in one TLS record.

  • connectReadOnlyIntent :: !Bool

    Declare the connection read-only in the login (what ApplicationIntent=ReadOnly does in other drivers), so that an Availability Group listener routes it to a readable secondary. A standalone server ignores it.

  • connectSessionSetup :: ![Text]

    Statements run after login, joined with newlines into one batch, and run again as one batch after each reset, which includes each release. With an empty list, reset costs no round trip of its own. See defaultSessionSetup for what the login already sets.

  • connectMars :: !Bool

    Multiple Active Result Sets: several responses can be open on the connection at once, each request on a session of its own, as SqlClient's MultipleActiveResultSets=True does. Off by default, so a new request abandons a response that is still open (ResultAbandoned). Hmssql.Connection has the rules. connect throws MarsNotSupported if the server doesn't support MARS.

  • connectMarsWindow :: !Int

    With connectMars: how many packets the server may send ahead of what has been read, on each open response; at least 4. It bounds what a response nobody is reading keeps in memory (the window times the packet size) and, over a long round trip, how fast one stream can be read. The default, 64, kept up with a plain connection over a 45 ms round trip.

  • connectPinReader :: !Bool

    Pin the connection's reader thread to the capability of the thread that calls connect, instead of leaving it unbound (the default). When that thread is the one that makes the requests, a local round trip with +RTS -N on many cores is 30 to 40% faster; but every connection a pool opens from one thread then has its reader on that one capability.

  • connectValueAllocAhead :: !Int

    How much buffer a large value, such as a row longer than 64 KiB or an output parameter, may be allocated before its bytes arrive. A value declared at most this long gets its whole buffer as it starts arriving, and a longer one grows as it arrives. The limit bounds what a server that declares a value it doesn't send can make the client allocate. 1 MiB by default, and 0 allocates nothing ahead.

  • connectOnInfoMessage :: !(ServerMessage -> IO ())

    Called for every INFO token (PRINT output, low-severity messages), on the thread reading the response, while it holds the connection. It must not use the connection (that throws ReentrantCallback) or wait for another thread that does (that deadlocks). An exception it throws propagates to the reader; the connection stays usable.

defaultConnectInfo :: ConnectInfo Source #

The defaults, which the example at the top of Hmssql starts from:

  • localhost on port 1433, over ProtocolAuto, to the default instance.
  • No user, password, access token or database.
  • EncryptOn with defaultTlsSettings: the whole session is encrypted, and the server's certificate is validated against the system store.
  • 8000-byte packets, no session setup and no MARS.

data Protocol Source #

What a connection travels on (connectProtocol).

Shared Memory and Named Pipes exist on Windows only; asking for one elsewhere makes connect throw. Both carry the same bytes as TCP, so encryption, logins and everything after work the same. On Windows under GHC's native I/O manager (+RTS --io-manager=native), TCP goes through network's own calls and the pipes through the manager when hmssql was built against network 3.3 or later with a GHC whose native manager network supports, with or without -threaded. Without -threaded, network's blocking calls (the lookup, the connect, and a send once the server stops reading) and the open of a pipe stop every Haskell thread while they block. A build that can't use the native manager needs -threaded to run under it.

Constructors

ProtocolAuto

On Windows: Shared Memory if connectHost is this machine (localhost, ., (local) or the machine's name), then TCP, then Named Pipes, the order Microsoft's clients use. The next is tried only when one can't be opened; a server that was reached and then refused the TLS handshake or the login is not tried another way. Elsewhere: TCP.

A connectPort other than the default, 1433, means TCP and nothing else, on Windows too: the port names one server, and a pipe might lead to another. With a connectInstance and the default port, only the pipes are tried, since TCP would need the instance's port.

ProtocolTcp

TCP, to connectHost and connectPort.

ProtocolSharedMemory

Shared Memory: a SQL Server on this machine, whatever connectHost says.

ProtocolNamedPipes

Named Pipes, at the pipe SQL Server listens on by default. On another machine the pipe is reached over SMB, so Windows first authenticates the user running the program to that machine.

ProtocolCustom (IO Backend)

A byte stream of the caller's making (Hmssql.Connection.Backend): the action connects and returns it, and connect closes it when the login fails or the connection is closed. It runs with asynchronous exceptions masked, and should close what it opened if it is interrupted. A login redirect is still followed over TCP.

withConnection :: ConnectInfo -> (Connection -> IO a) -> IO a Source #

Open a connection with connect, run the action with it, and close it afterward, whether the action returns or throws.

connect :: ConnectInfo -> IO Connection Source #

Open a connection and log in.

If the server can't be reached, this throws ConnectionClosedBy (TransportFailed e) with the underlying IOException. When ProtocolAuto tried several protocols, the exception says why each one failed. There is no built-in connect timeout, so wrap the call in timeout where one is needed. A host with several addresses gives each but the last 4 seconds to answer before the next is tried, so a timeout of 15 seconds still reaches a working address behind a silent one.

A login the server redirects, as Azure SQL does from inside Azure and an availability group listener does, is sent again to the host and port named, over TCP and with the same settings otherwise, up to ten times in a row. The eleventh redirect throws LoginRedirected, and so does a redirect received in plaintext under EncryptOff.

close :: Connection -> IO () Source #

Close the connection. It is safe to call from any thread, and more than once, through any handle to the connection, a released one included. A request in progress on another thread is interrupted and fails with ConnectionClosedBy ClosedByClient, as does any later use of the connection.

The TLS goodbye can block if the server doesn't read. It can be interrupted, and calling close again completes it. A closed connection keeps under a kilobyte alive, so one that a pool still references costs nothing.

release :: Connection -> IO Bool Source #

Reset the connection so that it can be returned to a connection pool. Return False if the connection is not usable, because it was closed, it is broken, or the reset itself failed, and so a later request could not use it; that means it should be closed instead of returned to the pool.

The reset does two things:

  • It cancels the active query, as cancelActiveStatement does, so that a stream that wasn't read to its end doesn't leave the connection busy. On a MARS connection, every open stream is canceled.
  • It marks the next request with the RESETCONNECTION bit, so that the server resets the session before running that request. That costs no round trip now. However, if release is called in the middle of a transaction, it resets the connection immediately instead, so that the transaction's locks are released before the connection idles in the pool.

Nothing is sent to probe the server. A session the server dropped without closing the socket is found by the next request, not here.

The handle given is released whether the cleanup succeeded or not. Further requests through it, and further reads from its streams, throw ConnectionReleased. close, renew and read-only inspection still work through it, and the next borrower gets a new handle from renew.

Like a request, release waits while another thread holds the connection, and it can be interrupted. An interrupted release leaves the connection broken, so a pool can wrap it in a timeout and treat Nothing as False:

ok <- fromMaybe False <$> timeout 5000000 (release conn)

Calling release inside withTransaction is not supported. Hmssql.Connection has a complete recipe for the resource-pool package.

renew :: Connection -> IO Connection Source #

Issue a new handle to the connection, for the next borrower from a pool, and make every earlier handle stale. A stale handle throws ConnectionReleased, so a borrower that kept its handle past release can't use the connection under the next borrower's feet. The connection itself is shared, and only the handle is new. Like a request, renew waits while another thread holds the connection. It sends nothing. The handle connect returns needs no renew.

A connection known to be unusable is refused with the exception a request would throw, ConnectionClosed or ConnectionBroken. The usual case is a session the server ended while the connection idled in the pool. The earlier handles are then left as they were, so close the connection and borrow another. unusableReason says what is known without throwing.

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

Whether the connection is known to be unusable, and why: it was closed, an earlier failure broke it, or the server ended it while it idled. Nothing means that no failure has been seen, not that the connection is healthy. Nothing is sent to probe the server, so a session the server dropped without closing the socket is found by the next request. Like a request, it waits while another thread holds the connection.

It describes the connection, not the handle. A released handle to a healthy connection gives Nothing, and a request through that handle still throws ConnectionReleased.

data Encryption Source #

How much of the connection is encrypted (connectEncryption). The first four are the values of the ENCRYPTION option of PRELOGIN, where the client and the server negotiate. EncryptStrict is TDS 8.0, which never negotiates.

Constructors

EncryptOff

Encrypt the login packet only, or the whole session if the server requires it. If the server doesn't support encryption at all, nothing is encrypted, not even the password. (0x00)

EncryptOn

Encrypt the whole session, and fail if the server doesn't support encryption. The default. (0x01)

EncryptNotSup

Encrypt nothing, and fail if the server requires encryption. (0x02)

EncryptReq

Encryption required. It behaves as EncryptOn does. (0x03)

EncryptStrict

TDS 8.0: TLS from the first byte, before any PRELOGIN, and a failure against a server that doesn't support TDS 8.0. (0x04, which is never sent, since there is no PRELOGIN to send it in)

Instances

Instances details
Show Encryption Source # 
Instance details

Defined in Hmssql.Tds.Client

Eq Encryption Source # 
Instance details

Defined in Hmssql.Tds.Client

data TlsSettings Source #

How the server's certificate is validated, in every mode that uses TLS. defaultTlsSettings validates against the system store; the fields add trust, override the name checked, or turn validation off.

Constructors

TlsSettings 

Fields

  • tlsTrustServerCertificate :: !Bool

    Skip certificate validation entirely. Encryption still applies, but the server's identity is not verified.

    A certificate whose extended key usage extension doesn't include server authentication is refused even so: tls checks that itself, whatever the validation decided. (SQL Server doesn't use such a certificate anyway: it requires a certificate for server authentication.) With tls 2.5 or later, so is a certificate whose RSA key is shorter than 2048 bits.

  • tlsUseSystemCertificateStore :: !Bool

    Trust the system's certificate store. It is loaded once per process, by the first connection that uses it (loading it takes about 20 ms and 38 MB of allocation on macOS, where it runs the security tool), so certificates added to the system store later are seen only by a new process.

  • tlsCaCertificateFiles :: ![FilePath]

    PEM files whose certificates are trusted in addition to the system store. A self-signed server certificate can be listed here directly. Each is read and parsed by every connection that uses it (about 70 us for a one-certificate file), so a replaced file takes effect with the next connection; an application that connects often and wants to parse a file once can read it itself (readSignedObject) and pass the certificates as tlsExtraCertificates.

  • tlsExtraCertificates :: ![SignedCertificate]

    Trusted certificates supplied in memory.

  • tlsHostNameInCertificate :: !(Maybe Text)

    Validate the certificate against this name instead of the host that was connected to. The server name sent in the TLS handshake (SNI) is still connectHost, or none if that is an IP address.

defaultTlsSettings :: TlsSettings Source #

Validate the server's certificate against the system store, for the host that was connected to, with no extra certificates trusted.

Building queries

data Query Source #

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.

Instances

Instances details
Monoid Query Source # 
Instance details

Defined in Hmssql.Query.Types

Methods

mempty :: Query #

mappend :: Query -> Query -> Query #

mconcat :: [Query] -> Query #

Semigroup Query Source # 
Instance details

Defined in Hmssql.Query.Types

Methods

(<>) :: Query -> Query -> Query #

sconcat :: NonEmpty Query -> Query #

stimes :: Integral b => b -> Query -> Query #

IsString Query Source # 
Instance details

Defined in Hmssql.Query.Types

Methods

fromString :: String -> Query #

Show Query Source # 
Instance details

Defined in Hmssql.Query.Types

Methods

showsPrec :: Int -> Query -> ShowS #

show :: Query -> String #

showList :: [Query] -> ShowS #

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 mkQuery text () for text without placeholders.

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.

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

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.

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.

Running queries

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.

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.

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.

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.

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))) parents

An 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.

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.

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 executeStatements conn (map (mkQuery sql) rows) with that sharing, and everything 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.

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

Instances details
Applicative ResultSets Source # 
Instance details

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 # 
Instance details

Defined in Hmssql.Query.Types

Methods

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

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

Monad ResultSets Source # 
Instance details

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.

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.

Transactions

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

Run the action in a transaction. It commits if the action returns, and rolls back if the action throws, an asynchronous exception from killThread or timeout included. Beginning the transaction is a round trip of its own before the action runs, and the COMMIT is another.

The connection is held for the whole transaction, so requests from other threads wait until it ends instead of running inside it. The action must not wait for another thread that uses the connection.

On a MARS connection (connectMars) every request the body makes, and every stream it opens, runs in the transaction. No response may be unfinished when it begins or when the body returns (TransactionRequestsActive): begin first, open the streams inside with withQueryS, and let them end before the body returns. If the body throws, the responses it left open are canceled before the rollback.

Throws TransactionAlreadyActive if a transaction is already active; TransactionEndedEarly (after cleaning up) if the body returns but the transaction it started is gone; and TransactionStillOpen (after rolling everything back) if the body began a nested transaction and didn't end it. If the rollback after a failure itself fails, the connection is marked broken (RollbackFailed) rather than left in an unknown state, and the exception that caused the rollback propagates, unless an asynchronous exception interrupted the rollback.

withTransactionLevel :: IsolationLevel -> Connection -> IO a -> IO a Source #

withTransaction at the given isolation level. The begin request sets the level, and the COMMIT or ROLLBACK batch sets it back to READ COMMITTED, the session's level at login, however the transaction ends, so no round trips are added. The session is assumed to be at READ COMMITTED outside withTransactionLevel, and a level the application set by hand is not preserved.

Rows and parameters

class FromSqlRow a where Source #

Types that decode from a whole row, one column per field in order. Tuples of up to twelve fields and Only have instances. Derive one for a record whose fields are FromSqlField types, one field per column of the SELECT, in order:

data Customer = Customer { customerId :: Int, name :: Text, birthDate :: Maybe Day }
  deriving stock (Show, Generic)
  deriving anyclass (FromSqlRow)

The column types are checked against the fields before the first row is decoded, and a mismatch is a DecodeError naming the column. For a row decoder written by hand, see singleField.

Minimal complete definition

Nothing

Methods

rowDecoder :: RowDecoder a Source #

default rowDecoder :: (Generic a, GRowDecoder (Rep a)) => RowDecoder a Source #

Instances

Instances details
FromSqlField a => FromSqlRow (Only a) Source # 
Instance details

Defined in Hmssql.Decoding

(FromSqlField a, FromSqlField b) => FromSqlRow (a, b) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b) Source #

(FromSqlField a, FromSqlField b, FromSqlField c) => FromSqlRow (a, b, c) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b, c) Source #

(FromSqlField a, FromSqlField b, FromSqlField c, FromSqlField d) => FromSqlRow (a, b, c, d) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b, c, d) Source #

(FromSqlField a, FromSqlField b, FromSqlField c, FromSqlField d, FromSqlField e) => FromSqlRow (a, b, c, d, e) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b, c, d, e) Source #

(FromSqlField a, FromSqlField b, FromSqlField c, FromSqlField d, FromSqlField e, FromSqlField f) => FromSqlRow (a, b, c, d, e, f) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b, c, d, e, f) Source #

(FromSqlField a, FromSqlField b, FromSqlField c, FromSqlField d, FromSqlField e, FromSqlField f, FromSqlField g) => FromSqlRow (a, b, c, d, e, f, g) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b, c, d, e, f, g) Source #

(FromSqlField a, FromSqlField b, FromSqlField c, FromSqlField d, FromSqlField e, FromSqlField f, FromSqlField g, FromSqlField h) => FromSqlRow (a, b, c, d, e, f, g, h) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b, c, d, e, f, g, h) Source #

(FromSqlField a, FromSqlField b, FromSqlField c, FromSqlField d, FromSqlField e, FromSqlField f, FromSqlField g, FromSqlField h, FromSqlField i) => FromSqlRow (a, b, c, d, e, f, g, h, i) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b, c, d, e, f, g, h, i) Source #

(FromSqlField a, FromSqlField b, FromSqlField c, FromSqlField d, FromSqlField e, FromSqlField f, FromSqlField g, FromSqlField h, FromSqlField i, FromSqlField j) => FromSqlRow (a, b, c, d, e, f, g, h, i, j) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b, c, d, e, f, g, h, i, j) Source #

(FromSqlField a, FromSqlField b, FromSqlField c, FromSqlField d, FromSqlField e, FromSqlField f, FromSqlField g, FromSqlField h, FromSqlField i, FromSqlField j, FromSqlField k) => FromSqlRow (a, b, c, d, e, f, g, h, i, j, k) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b, c, d, e, f, g, h, i, j, k) Source #

(FromSqlField a, FromSqlField b, FromSqlField c, FromSqlField d, FromSqlField e, FromSqlField f, FromSqlField g, FromSqlField h, FromSqlField i, FromSqlField j, FromSqlField k, FromSqlField l) => FromSqlRow (a, b, c, d, e, f, g, h, i, j, k, l) Source # 
Instance details

Defined in Hmssql.Decoding

Methods

rowDecoder :: RowDecoder (a, b, c, d, e, f, g, h, i, j, k, l) Source #

class FromSqlField a where Source #

Types that decode from one column. The usual Haskell types have instances, and Hmssql.Types has the table of which SQL types each accepts. Maybe accepts NULL; without it, a NULL is a DecodeError. An instance of your own defines fieldDecoder, usually with refineField over a built-in type's decoder.

Minimal complete definition

fieldDecoder

Methods

fieldDecoder :: FieldDecoder a Source #

The decoder of one column of this type.

fieldRowDecoder :: RowDecoder a Source #

A row decoder of one column of this type, the same as singleField fieldDecoder. Tuples, Only and derived FromSqlRow instances read their columns with it, and the built-in types' are faster than singleField. An instance doesn't define it.

Instances

Instances details
FromSqlField Decimal Source #

Accepts decimal, numeric, money, smallmoney and integer types.

Instance details

Defined in Hmssql.Decoding

FromSqlField ByteString Source #

Accepts binary types and also the raw bytes of single-byte character types.

Instance details

Defined in Hmssql.Decoding

FromSqlField ByteString Source #

As a strict ByteString, with the whole value read into memory first. To read a large value as it arrives, use streamField.

Instance details

Defined in Hmssql.Decoding

FromSqlField Int16 Source #

bit and the integer types.

Instance details

Defined in Hmssql.Decoding

FromSqlField Int32 Source #

bit and the integer types.

Instance details

Defined in Hmssql.Decoding

FromSqlField Int64 Source #

bit and the integer types.

Instance details

Defined in Hmssql.Decoding

FromSqlField Int8 Source #

bit and the integer types.

Instance details

Defined in Hmssql.Decoding

FromSqlField Word8 Source #

bit and the integer types.

Instance details

Defined in Hmssql.Decoding

FromSqlField RawValue Source # 
Instance details

Defined in Hmssql.Decoding

FromSqlField SqlValue Source # 
Instance details

Defined in Hmssql.Decoding

FromSqlField DateTimeOffset Source # 
Instance details

Defined in Hmssql.Decoding

FromSqlField Money Source #

money and smallmoney, with all four decimal places.

Instance details

Defined in Hmssql.Decoding

FromSqlField Text Source #

Accepts Unicode types, and single-byte types in UTF-8 or a supported single-byte code page. The collation is checked when the result set's columns arrive, before any rows are decoded.

Instance details

Defined in Hmssql.Decoding

FromSqlField Text Source #

As Text, with the whole value read into memory first. To read a large value as it arrives, use streamTextField.

Instance details

Defined in Hmssql.Decoding

FromSqlField Day Source #

date.

Instance details

Defined in Hmssql.Decoding

FromSqlField UTCTime Source #

Zoneless types are taken to be UTC; datetimeoffset is converted to UTC.

Instance details

Defined in Hmssql.Decoding

FromSqlField LocalTime Source #

datetime, smalldatetime, datetime2 and date (at midnight).

Instance details

Defined in Hmssql.Decoding

FromSqlField TimeOfDay Source #

time at any scale.

Instance details

Defined in Hmssql.Decoding

FromSqlField ZonedTime Source # 
Instance details

Defined in Hmssql.Decoding

FromSqlField UUID Source #

uniqueidentifier.

Instance details

Defined in Hmssql.Decoding

FromSqlField Integer Source #

Integer types, and decimal / numeric with scale 0 (such as decimal(38,0), which Integer parameters beyond bigint are sent as).

Instance details

Defined in Hmssql.Decoding

FromSqlField String Source # 
Instance details

Defined in Hmssql.Decoding

FromSqlField Bool Source #

bit.

Instance details

Defined in Hmssql.Decoding

FromSqlField Double Source #

real and float.

Instance details

Defined in Hmssql.Decoding

FromSqlField Float Source #

real. A float value that takes eight bytes is a DecodeError, rather than being narrowed.

Instance details

Defined in Hmssql.Decoding

FromSqlField Int Source #

bit and the integer types. A value out of range is a DecodeError, as it is for the other fixed-size integers below.

Instance details

Defined in Hmssql.Decoding

FromSqlField a => FromSqlField (Maybe a) Source # 
Instance details

Defined in Hmssql.Decoding

class ToSqlRow a where Source #

Types that can be sent as a list of parameters: the @p1, @p2, ... of mkQuery, or a row of executeMany or bulkInsert. Derive it for a record whose fields are ToSqlField types, one parameter per field in order:

data NewCustomer = NewCustomer { newName :: Text, newBirthDate :: Maybe Day }
  deriving stock (Generic)
  deriving anyclass (ToSqlRow)

Tuples of up to ten fields, (), Only, a list of one ToSqlField type, a list of SqlParam to mix types, and NamedParams have instances.

Minimal complete definition

Nothing

Methods

rowEncoder :: RowEncoder a Source #

default rowEncoder :: (Generic a, GRowEncoder (Rep a)) => RowEncoder a Source #

Instances

Instances details
ToSqlRow NamedParams Source # 
Instance details

Defined in Hmssql.Encoding

ToSqlRow () Source # 
Instance details

Defined in Hmssql.Encoding

ToSqlField a => ToSqlRow (Only a) Source # 
Instance details

Defined in Hmssql.Encoding

ToSqlField a => ToSqlRow [a] Source #

Any number of parameters of the same type.

Instance details

Defined in Hmssql.Encoding

(ToSqlField a, ToSqlField b) => ToSqlRow (a, b) Source # 
Instance details

Defined in Hmssql.Encoding

Methods

rowEncoder :: RowEncoder (a, b) Source #

(ToSqlField a, ToSqlField b, ToSqlField c) => ToSqlRow (a, b, c) Source # 
Instance details

Defined in Hmssql.Encoding

Methods

rowEncoder :: RowEncoder (a, b, c) Source #

(ToSqlField a, ToSqlField b, ToSqlField c, ToSqlField d) => ToSqlRow (a, b, c, d) Source # 
Instance details

Defined in Hmssql.Encoding

Methods

rowEncoder :: RowEncoder (a, b, c, d) Source #

(ToSqlField a, ToSqlField b, ToSqlField c, ToSqlField d, ToSqlField e) => ToSqlRow (a, b, c, d, e) Source # 
Instance details

Defined in Hmssql.Encoding

Methods

rowEncoder :: RowEncoder (a, b, c, d, e) Source #

(ToSqlField a, ToSqlField b, ToSqlField c, ToSqlField d, ToSqlField e, ToSqlField f) => ToSqlRow (a, b, c, d, e, f) Source # 
Instance details

Defined in Hmssql.Encoding

Methods

rowEncoder :: RowEncoder (a, b, c, d, e, f) Source #

(ToSqlField a, ToSqlField b, ToSqlField c, ToSqlField d, ToSqlField e, ToSqlField f, ToSqlField g) => ToSqlRow (a, b, c, d, e, f, g) Source # 
Instance details

Defined in Hmssql.Encoding

Methods

rowEncoder :: RowEncoder (a, b, c, d, e, f, g) Source #

(ToSqlField a, ToSqlField b, ToSqlField c, ToSqlField d, ToSqlField e, ToSqlField f, ToSqlField g, ToSqlField h) => ToSqlRow (a, b, c, d, e, f, g, h) Source # 
Instance details

Defined in Hmssql.Encoding

Methods

rowEncoder :: RowEncoder (a, b, c, d, e, f, g, h) Source #

(ToSqlField a, ToSqlField b, ToSqlField c, ToSqlField d, ToSqlField e, ToSqlField f, ToSqlField g, ToSqlField h, ToSqlField i) => ToSqlRow (a, b, c, d, e, f, g, h, i) Source # 
Instance details

Defined in Hmssql.Encoding

Methods

rowEncoder :: RowEncoder (a, b, c, d, e, f, g, h, i) Source #

(ToSqlField a, ToSqlField b, ToSqlField c, ToSqlField d, ToSqlField e, ToSqlField f, ToSqlField g, ToSqlField h, ToSqlField i, ToSqlField j) => ToSqlRow (a, b, c, d, e, f, g, h, i, j) Source # 
Instance details

Defined in Hmssql.Encoding

Methods

rowEncoder :: RowEncoder (a, b, c, d, e, f, g, h, i, j) Source #

class ToSqlField a where Source #

Types that can be sent as one parameter, through param or as a field of a ToSqlRow value. The usual Haskell types have instances, and Hmssql.Types has the table of which SQL type each is sent as. An instance of your own defines fieldEncoder, usually with contramap over a built-in type's encoder.

Instances

Instances details
ToSqlField Decimal Source #

decimal(p,s) with the value's own scale s and just enough precision p for its digits (so 0.001 is decimal(3,3), 1000000 decimal(7,0)). Values needing more than 38 digits, or a scale above 38, are rejected.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Decimal Source #

fieldParam :: Decimal -> FieldParams -> FieldParams

ToSqlField ByteString Source #

Any length of bytes. A value of up to 8000 bytes is declared varbinary(8000), and a longer one or a NULL (Nothing) is declared varbinary(max). Hmssql.Types says when the difference matters.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder ByteString Source #

fieldParam :: ByteString -> FieldParams -> FieldParams

ToSqlField ByteString Source #

Declared as a strict ByteString is, by the same rule. A value too long for varbinary(8000) is forced as it is sent, so it needn't all be in memory. A shorter one is forced before sending. Choosing the declaration forces the value as far as its 8001st byte or its end.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder ByteString Source #

fieldParam :: ByteString -> FieldParams -> FieldParams

ToSqlField Int16 Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Int16 Source #

fieldParam :: Int16 -> FieldParams -> FieldParams

ToSqlField Int32 Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Int32 Source #

fieldParam :: Int32 -> FieldParams -> FieldParams

ToSqlField Int64 Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Int64 Source #

fieldParam :: Int64 -> FieldParams -> FieldParams

ToSqlField Int8 Source #

smallint: tinyint is unsigned, so it can't hold negative values.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Int8 Source #

fieldParam :: Int8 -> FieldParams -> FieldParams

ToSqlField Word8 Source #

tinyint.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Word8 Source #

fieldParam :: Word8 -> FieldParams -> FieldParams

ToSqlField RawParam Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder RawParam Source #

fieldParam :: RawParam -> FieldParams -> FieldParams

ToSqlField SqlParam Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder SqlParam Source #

fieldParam :: SqlParam -> FieldParams -> FieldParams

ToSqlField StreamParam Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder StreamParam Source #

fieldParam :: StreamParam -> FieldParams -> FieldParams

ToSqlField SqlValue Source #

Each constructor is sent as the wire type FromSqlField SqlValue decodes it from, so SELECT @p1 returns the same SqlValue: SqlXml as xml (UTF-16 with a byte order mark, no schema collection), SqlText as nvarchar, and so on. SqlNull is an nvarchar(max) NULL, which the server won't convert to sql_variant (error 206).

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder SqlValue Source #

fieldParam :: SqlValue -> FieldParams -> FieldParams

ToSqlField DateTimeOffset Source #

datetimeoffset(7), rounded like LocalTime. The offset must be within ±14:00 and both the UTC and the local time within years 1 through 9999.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder DateTimeOffset Source #

fieldParam :: DateTimeOffset -> FieldParams -> FieldParams

ToSqlField Money Source #

money; values outside its range (±922,337,203,685,477.58) are rejected.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Money Source #

fieldParam :: Money -> FieldParams -> FieldParams

ToSqlField Text Source #

Any length of string. A value of up to 4000 UTF-16 code units is declared nvarchar(4000), and a longer one or a NULL (Nothing) is declared nvarchar(max). Hmssql.Types says when the difference matters.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Text Source #

fieldParam :: Text -> FieldParams -> FieldParams

ToSqlField Text Source #

Declared as Text is, by the same rule. A value too long for nvarchar(4000) is forced as it is sent, so it needn't all be in memory. A shorter one is forced before sending. Choosing the declaration forces the value as far as its 4001st code unit or its end.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Text Source #

fieldParam :: Text -> FieldParams -> FieldParams

ToSqlField Day Source #

date, years 1 through 9999.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Day Source #

fieldParam :: Day -> FieldParams -> FieldParams

ToSqlField UTCTime Source #

datetime2(7) holding the UTC clock time (zoneless).

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder UTCTime Source #

fieldParam :: UTCTime -> FieldParams -> FieldParams

ToSqlField LocalTime Source #

datetime2(7), rounded to 100 ns (carrying into the next day; a leap second counts as the following second). Years 1 through 9999.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder LocalTime Source #

fieldParam :: LocalTime -> FieldParams -> FieldParams

ToSqlField TimeOfDay Source #

time(7), rounded to 100 ns but never past 23:59:59.9999999 (there is no next day to carry into; a leap second also becomes 23:59:59.9999999).

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder TimeOfDay Source #

fieldParam :: TimeOfDay -> FieldParams -> FieldParams

ToSqlField ZonedTime Source #

As DateTimeOffset. The offset is checked before it is narrowed to the wire's 16-bit minute count.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder ZonedTime Source #

fieldParam :: ZonedTime -> FieldParams -> FieldParams

ToSqlField UUID Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder UUID Source #

fieldParam :: UUID -> FieldParams -> FieldParams

ToSqlField Integer Source #

bigint, or decimal(38,0) beyond its range; values of 39 or more digits are rejected.

Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Integer Source #

fieldParam :: Integer -> FieldParams -> FieldParams

ToSqlField String Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder String Source #

fieldParam :: String -> FieldParams -> FieldParams

ToSqlField Bool Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Bool Source #

fieldParam :: Bool -> FieldParams -> FieldParams

ToSqlField Double Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Double Source #

fieldParam :: Double -> FieldParams -> FieldParams

ToSqlField Float Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Float Source #

fieldParam :: Float -> FieldParams -> FieldParams

ToSqlField Int Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder Int Source #

fieldParam :: Int -> FieldParams -> FieldParams

ToSqlField a => ToSqlField (Maybe a) Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder (Maybe a) Source #

fieldParam :: Maybe a -> FieldParams -> FieldParams

newtype Only a #

The 1-tuple type or single-value "collection".

This type is structurally equivalent to the Identity type, but its intent is more about serving as the anonymous 1-tuple type missing from Haskell for attaching typeclass instances.

Parameter usage example:

encodeSomething (Only (42::Int))

Result usage example:

xs <- decodeSomething
forM_ xs $ \(Only id) -> {- ... -}

Constructors

Only 

Fields

Instances

Instances details
Functor Only # 
Instance details

Defined in Data.Tuple.Only

Methods

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

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

NFData a => NFData (Only a) # 
Instance details

Defined in Data.Tuple.Only

Methods

rnf :: Only a -> () #

Data a => Data (Only a) # 
Instance details

Defined in Data.Tuple.Only

Methods

gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g. g -> c g) -> Only a -> c (Only a) #

gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c (Only a) #

toConstr :: Only a -> Constr #

dataTypeOf :: Only a -> DataType #

dataCast1 :: Typeable t => (forall d. Data d => c (t d)) -> Maybe (c (Only a)) #

dataCast2 :: Typeable t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c (Only a)) #

gmapT :: (forall b. Data b => b -> b) -> Only a -> Only a #

gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Only a -> r #

gmapQr :: forall r r'. (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Only a -> r #

gmapQ :: (forall d. Data d => d -> u) -> Only a -> [u] #

gmapQi :: Int -> (forall d. Data d => d -> u) -> Only a -> u #

gmapM :: Monad m => (forall d. Data d => d -> m d) -> Only a -> m (Only a) #

gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> Only a -> m (Only a) #

gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> Only a -> m (Only a) #

Generic (Only a) # 
Instance details

Defined in Data.Tuple.Only

Associated Types

type Rep (Only a) 
Instance details

Defined in Data.Tuple.Only

type Rep (Only a) = D1 ('MetaData "Only" "Data.Tuple.Only" "Only-0.1-KByY9gW5FGHAM3SqQ7lAVD" 'True) (C1 ('MetaCons "Only" 'PrefixI 'True) (S1 ('MetaSel ('Just "fromOnly") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 a)))

Methods

from :: Only a -> Rep (Only a) x #

to :: Rep (Only a) x -> Only a #

Read a => Read (Only a) # 
Instance details

Defined in Data.Tuple.Only

Show a => Show (Only a) # 
Instance details

Defined in Data.Tuple.Only

Methods

showsPrec :: Int -> Only a -> ShowS #

show :: Only a -> String #

showList :: [Only a] -> ShowS #

Eq a => Eq (Only a) # 
Instance details

Defined in Data.Tuple.Only

Methods

(==) :: Only a -> Only a -> Bool #

(/=) :: Only a -> Only a -> Bool #

Ord a => Ord (Only a) # 
Instance details

Defined in Data.Tuple.Only

Methods

compare :: Only a -> Only a -> Ordering #

(<) :: Only a -> Only a -> Bool #

(<=) :: Only a -> Only a -> Bool #

(>) :: Only a -> Only a -> Bool #

(>=) :: Only a -> Only a -> Bool #

max :: Only a -> Only a -> Only a #

min :: Only a -> Only a -> Only a #

FromSqlField a => FromSqlRow (Only a) Source # 
Instance details

Defined in Hmssql.Decoding

ToSqlField a => ToSqlRow (Only a) Source # 
Instance details

Defined in Hmssql.Encoding

type Rep (Only a) # 
Instance details

Defined in Data.Tuple.Only

type Rep (Only a) = D1 ('MetaData "Only" "Data.Tuple.Only" "Only-0.1-KByY9gW5FGHAM3SqQ7lAVD" 'True) (C1 ('MetaCons "Only" 'PrefixI 'True) (S1 ('MetaSel ('Just "fromOnly") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 a)))

data SqlParam Source #

Any value with a ToSqlField instance, so a parameter list can mix types.

Constructors

ToSqlField a => SqlParam a 

Instances

Instances details
ToSqlField SqlParam Source # 
Instance details

Defined in Hmssql.Encoding

Methods

fieldEncoder :: FieldEncoder SqlParam Source #

fieldParam :: SqlParam -> FieldParams -> FieldParams

newtype NamedParams Source #

Parameters that the SQL text refers to by name. The leading @ is optional. A name must be a T-SQL variable name, and not of the form @pN, which is reserved for the positional parameters; the query functions check both before sending anything. Bulk copy matches the names, with that one @ removed, against the table's columns (Hmssql.BulkCopy).

mkQuery "UPDATE dbo.customer SET email = @email WHERE id = @id"
  (NamedParams [("email", SqlParam newEmail), ("id", SqlParam customerId)])

Constructors

NamedParams [NamedParam] 

Instances

Instances details
ToSqlRow NamedParams Source # 
Instance details

Defined in Hmssql.Encoding