-- | 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 <https://github.com/mzabani/hpgsql 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 'Hmssql.Errors.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#g:pools" 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 'Hmssql.Errors.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;
-- 'Hmssql.Query.queryMulti', for result sets whose columns are known
-- only at runtime; 'Hmssql.Query.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 'Streaming.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 'Hmssql.Errors.ResultAbandoned'.  A
-- connection opened with 'connectMars' keeps several streams open at
-- once, and "Hmssql.Connection#mars" 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#g:streaming".  "Hmssql.Encoding#g:streaming" 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', 'Data.Text.Text', 'Double',
-- 'Data.Time.Day', 'Data.Time.LocalTime', 'Data.ByteString.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
-- 'Hmssql.Decoding.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 'Hmssql.Transaction.begin',
-- 'Hmssql.Transaction.commit' and 'Hmssql.Transaction.rollback'.
--
-- = Handling errors
--
-- Every exception hmssql throws is in "Hmssql.Errors".  After a
-- 'Hmssql.Errors.SqlServerError', which means the server reported an
-- error, or after a 'Hmssql.Errors.DecodeError',
-- 'Hmssql.Errors.EncodeError' or 'Hmssql.Errors.UnexpectedResult', the
-- connection is still usable.  After a 'Hmssql.Errors.ConnectionClosed',
-- 'Hmssql.Errors.ConnectionBroken' or 'Hmssql.Errors.ProtocolError' it is
-- not: close it and open another.
--
-- Nothing here takes a timeout argument.  Interrupt a call with an
-- asynchronous exception instead, from 'System.Timeout.timeout' or
-- 'Control.Concurrent.killThread'.  The statement is canceled on the
-- server, and the connection stays usable.
--
-- = The other modules
--
-- * "Hmssql.Connection": the connection options in full, the pool recipe,
--   the MARS rules, and the negotiated protocol state.
-- * "Hmssql.Query": building and running queries, in full, and the
--   low-level request and response API.
-- * "Hmssql.BatchQuery": several statements in one round trip, each with
--   its own result.
-- * "Hmssql.BulkCopy": loading many rows into one table.
-- * "Hmssql.Transaction": manual transactions, and the transaction
--   exceptions.
-- * "Hmssql.Decoding" and "Hmssql.Encoding": decoders and encoders of your
--   own, and streaming one large value.
-- * "Hmssql.SqlValue": a value of any SQL type.  "Hmssql.Types": hmssql's
--   own value types, and the tables of SQL and Haskell types.
-- * "Hmssql.Errors": every exception.
-- * "Hmssql.Catalog": a database's tables and views, with their columns,
--   keys and foreign keys, as data.  "Hmssql.SqlType": SQL Server's data
--   types by name.
-- * "Hmssql.Query.Types": the representations of the query types, for
--   libraries built on hmssql.  @Hmssql.Tds.*@: the wire protocol.
module Hmssql
  ( -- * Connections
    Connection
  , ConnectInfo (..)
  , defaultConnectInfo
  , Protocol (..)
  , withConnection
  , connect
  , close
  , release
  , renew
  , unusableReason
  , Encryption (..)
  , TlsSettings (..)
  , defaultTlsSettings

    -- * Building queries
  , Query
  , mkQuery
  , param
  , namedParam
  , escapeIdentifier
  , rawSql
  , queryText

    -- * Running queries
  , execute
  , execute_
  , query
  , query1
  , queryMay
  , queryS
  , withQueryS
  , queryFold
  , executeMany
  , executeStatements
    -- ** Several result sets
  , ResultSets
  , resultSet
  , skipResultSet
  , queryResultSets

    -- * Transactions
  , withTransaction
  , withTransactionLevel
  , IsolationLevel (..)

    -- * Rows and parameters
  , FromSqlRow (..)
  , FromSqlField (..)
  , ToSqlRow (..)
  , ToSqlField (..)
  , Only (..)
  , SqlParam (..)
  , NamedParams (..)
  ) where

import Data.Tuple.Only (Only (..))

import Hmssql.Connection
import Hmssql.Decoding (FromSqlField (..), FromSqlRow (..))
import Hmssql.Encoding (NamedParams (..), SqlParam (..), ToSqlField (..), ToSqlRow (..))
import Hmssql.Query
import Hmssql.Transaction