{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}

module Database.Persist.SqlBackend.Internal where

import Data.List.NonEmpty (NonEmpty)
import Data.Text (Text)
import Data.Vault.Strict (Vault)
import qualified Data.Vault.Strict as Vault
import Database.Persist.Class.PersistStore
import Database.Persist.Names
import Database.Persist.SqlBackend.Internal.InsertSqlResult
import Database.Persist.SqlBackend.Internal.IsolationLevel
import Database.Persist.SqlBackend.Internal.MkSqlBackend
import Database.Persist.SqlBackend.Internal.Statement
import Database.Persist.SqlBackend.StatementCache
import Database.Persist.Types.Base

-- | A 'SqlBackend' represents a handle or connection to a database. It
-- contains functions and values that allow databases to have more
-- optimized implementations, as well as references that benefit
-- performance and sharing.
--
-- Instead of using the 'SqlBackend' constructor directly, use the
-- 'mkSqlBackend' function.
--
-- A 'SqlBackend' is *not* thread-safe. You should not assume that
-- a 'SqlBackend' can be shared among threads and run concurrent queries.
-- This *will* result in problems. Instead, you should create a @'Pool'
-- 'SqlBackend'@, known as a 'ConnectionPool', and pass that around in
-- multi-threaded applications.
--
-- To run actions in the @persistent@ library, you should use the
-- 'runSqlConn' function. If you're using a multithreaded application, use
-- the 'runSqlPool' function.
data SqlBackend = SqlBackend
    { SqlBackend -> Text -> IO Statement
connPrepare :: Text -> IO Statement
    -- ^ This function should prepare a 'Statement' in the target database,
    -- which should allow for efficient query reuse.
    , SqlBackend -> EntityDef -> [PersistValue] -> InsertSqlResult
connInsertSql :: EntityDef -> [PersistValue] -> InsertSqlResult
    -- ^ This function generates the SQL and values necessary for
    -- performing an insert against the database.
    , SqlBackend
-> Maybe (EntityDef -> [[PersistValue]] -> InsertSqlResult)
connInsertManySql :: Maybe (EntityDef -> [[PersistValue]] -> InsertSqlResult)
    -- ^ SQL for inserting many rows and returning their primary keys, for
    -- backends that support this functionality. If 'Nothing', rows will be
    -- inserted one-at-a-time using 'connInsertSql'.
    , SqlBackend
-> Maybe
     (EntityDef -> NonEmpty (FieldNameHS, FieldNameDB) -> Text -> Text)
connUpsertSql :: Maybe (EntityDef -> NonEmpty (FieldNameHS, FieldNameDB) -> Text -> Text)
    -- ^ Some databases support performing UPSERT _and_ RETURN entity
    -- in a single call.
    --
    -- This field when set will be used to generate the UPSERT+RETURN sql given
    -- * an entity definition
    -- * updates to be run on unique key(s) collision
    --
    -- When left as 'Nothing', we find the unique key from entity def before
    -- * trying to fetch an entity by said key
    -- * perform an update when result found, else issue an insert
    -- * return new entity from db
    --
    -- @since 2.6
    , SqlBackend -> Maybe (EntityDef -> Int -> Text)
connPutManySql :: Maybe (EntityDef -> Int -> Text)
    -- ^ Some databases support performing bulk UPSERT, specifically
    -- "insert or replace many records" in a single call.
    --
    -- This field when set, given
    -- * an entity definition
    -- * number of records to be inserted
    -- should produce a PUT MANY sql with placeholders for records
    --
    -- When left as 'Nothing', we default to using 'defaultPutMany'.
    --
    -- @since 2.8.1
    , SqlBackend -> StatementCache
connStmtMap :: StatementCache
    -- ^ A reference to the cache of statements. 'Statement's are keyed by
    -- the 'Text' queries that generated them.
    , SqlBackend -> IO ()
connClose :: IO ()
    -- ^ Close the underlying connection.
    , SqlBackend
-> [EntityDef]
-> (Text -> IO Statement)
-> EntityDef
-> IO (Either [Text] [(Bool, Text)])
connMigrateSql
        :: [EntityDef]
        -> (Text -> IO Statement)
        -> EntityDef
        -> IO (Either [Text] [(Bool, Text)])
    -- ^ This function returns the migrations required to include the
    -- 'EntityDef' parameter in the @['EntityDef']@ database. This might
    -- include creating a new table if the entity is not present, or
    -- altering an existing table if it is.
    , SqlBackend
-> (Text -> IO Statement) -> Maybe IsolationLevel -> IO ()
connBegin :: (Text -> IO Statement) -> Maybe IsolationLevel -> IO ()
    -- ^ A function to begin a transaction for the underlying database.
    , SqlBackend -> (Text -> IO Statement) -> IO ()
connCommit :: (Text -> IO Statement) -> IO ()
    -- ^ A function to commit a transaction to the underlying database.
    , SqlBackend -> (Text -> IO Statement) -> IO ()
connRollback :: (Text -> IO Statement) -> IO ()
    -- ^ A function to roll back a transaction on the underlying database.
    , SqlBackend -> FieldNameDB -> Text
connEscapeFieldName :: FieldNameDB -> Text
    -- ^ A function to extract and escape the name of the column corresponding
    -- to the provided field.
    --
    -- @since 2.12.0.0
    , SqlBackend -> EntityDef -> Text
connEscapeTableName :: EntityDef -> Text
    -- ^ A function to extract and escape the name of the table corresponding
    -- to the provided entity. PostgreSQL uses this to support schemas.
    --
    -- @since 2.12.0.0
    , SqlBackend -> Text -> Text
connEscapeRawName :: Text -> Text
    -- ^ A function to escape raw DB identifiers. MySQL uses backticks, while
    -- PostgreSQL uses quotes, and so on.
    --
    -- @since 2.12.0.0
    , SqlBackend -> Text
connNoLimit :: Text
    , SqlBackend -> Text
connRDBMS :: Text
    -- ^ A tag displaying what database the 'SqlBackend' is for. Can be
    -- used to differentiate features in downstream libraries for different
    -- database backends.
    , SqlBackend -> (Int, Int) -> Text -> Text
connLimitOffset :: (Int,Int) -> Text -> Text
    -- ^ Attach a 'LIMIT/OFFSET' clause to a SQL query. Note that
    -- LIMIT/OFFSET is problematic for performance, and indexed range
    -- queries are the superior way to offer pagination.
    , SqlBackend -> LogFunc
connLogFunc :: LogFunc
    -- ^ A log function for the 'SqlBackend' to use.
    , SqlBackend -> Maybe Int
connMaxParams :: Maybe Int
    -- ^ Some databases (probably only Sqlite) have a limit on how
    -- many question-mark parameters may be used in a statement
    --
    -- @since 2.6.1
    , SqlBackend -> Maybe (EntityDef -> Int -> Text)
connRepsertManySql :: Maybe (EntityDef -> Int -> Text)
    -- ^ Some databases support performing bulk an atomic+bulk INSERT where
    -- constraint conflicting entities can replace existing entities.
    --
    -- This field when set, given
    -- * an entity definition
    -- * number of records to be inserted
    -- should produce a INSERT sql with placeholders for primary+record fields
    --
    -- When left as 'Nothing', we default to using 'defaultRepsertMany'.
    --
    -- @since 2.9.0
    , SqlBackend -> Vault
connVault :: Vault
    -- ^ Carry arbitrary payloads for the connection that
    -- may be used to propagate information into hooks.
    , SqlBackend -> SqlBackendHooks
connHooks :: SqlBackendHooks
    -- ^ Instrumentation hooks that may be used to track the
    -- behaviour of a backend.
    }

newtype SqlBackendHooks = SqlBackendHooks
    { SqlBackendHooks -> SqlBackend -> Text -> Statement -> IO Statement
hookGetStatement :: SqlBackend -> Text -> Statement -> IO Statement
    }

emptySqlBackendHooks :: SqlBackendHooks
emptySqlBackendHooks :: SqlBackendHooks
emptySqlBackendHooks = SqlBackendHooks
    { hookGetStatement :: SqlBackend -> Text -> Statement -> IO Statement
hookGetStatement = \SqlBackend
_ Text
_ Statement
s -> Statement -> IO Statement
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Statement
s
    }

-- | A function for creating a value of the 'SqlBackend' type. You should prefer
-- to use this instead of the constructor for 'SqlBackend', because default
-- values for this will be provided for new fields on the record when new
-- functionality is added.
--
-- @since 2.13.0.0
mkSqlBackend :: MkSqlBackendArgs -> SqlBackend
mkSqlBackend :: MkSqlBackendArgs -> SqlBackend
mkSqlBackend MkSqlBackendArgs {IO ()
Text
IORef (Map Text Statement)
[EntityDef]
-> (Text -> IO Statement)
-> EntityDef
-> IO (Either [Text] [(Bool, Text)])
(Int, Int) -> Text -> Text
Text -> IO Statement
Text -> Text
LogFunc
FieldNameDB -> Text
EntityDef -> Text
EntityDef -> [PersistValue] -> InsertSqlResult
(Text -> IO Statement) -> IO ()
(Text -> IO Statement) -> Maybe IsolationLevel -> IO ()
connInsertSql :: MkSqlBackendArgs -> EntityDef -> [PersistValue] -> InsertSqlResult
connPrepare :: Text -> IO Statement
connInsertSql :: EntityDef -> [PersistValue] -> InsertSqlResult
connStmtMap :: IORef (Map Text Statement)
connClose :: IO ()
connMigrateSql :: [EntityDef]
-> (Text -> IO Statement)
-> EntityDef
-> IO (Either [Text] [(Bool, Text)])
connBegin :: (Text -> IO Statement) -> Maybe IsolationLevel -> IO ()
connCommit :: (Text -> IO Statement) -> IO ()
connRollback :: (Text -> IO Statement) -> IO ()
connEscapeFieldName :: FieldNameDB -> Text
connEscapeTableName :: EntityDef -> Text
connEscapeRawName :: Text -> Text
connNoLimit :: Text
connRDBMS :: Text
connLimitOffset :: (Int, Int) -> Text -> Text
connLogFunc :: LogFunc
connPrepare :: MkSqlBackendArgs -> Text -> IO Statement
connStmtMap :: MkSqlBackendArgs -> IORef (Map Text Statement)
connClose :: MkSqlBackendArgs -> IO ()
connMigrateSql :: MkSqlBackendArgs
-> [EntityDef]
-> (Text -> IO Statement)
-> EntityDef
-> IO (Either [Text] [(Bool, Text)])
connBegin :: MkSqlBackendArgs
-> (Text -> IO Statement) -> Maybe IsolationLevel -> IO ()
connCommit :: MkSqlBackendArgs -> (Text -> IO Statement) -> IO ()
connRollback :: MkSqlBackendArgs -> (Text -> IO Statement) -> IO ()
connEscapeFieldName :: MkSqlBackendArgs -> FieldNameDB -> Text
connEscapeTableName :: MkSqlBackendArgs -> EntityDef -> Text
connEscapeRawName :: MkSqlBackendArgs -> Text -> Text
connNoLimit :: MkSqlBackendArgs -> Text
connRDBMS :: MkSqlBackendArgs -> Text
connLimitOffset :: MkSqlBackendArgs -> (Int, Int) -> Text -> Text
connLogFunc :: MkSqlBackendArgs -> LogFunc
..} =
    SqlBackend
        { connMaxParams :: Maybe Int
connMaxParams = Maybe Int
forall a. Maybe a
Nothing
        , connRepsertManySql :: Maybe (EntityDef -> Int -> Text)
connRepsertManySql = Maybe (EntityDef -> Int -> Text)
forall a. Maybe a
Nothing
        , connPutManySql :: Maybe (EntityDef -> Int -> Text)
connPutManySql = Maybe (EntityDef -> Int -> Text)
forall a. Maybe a
Nothing
        , connUpsertSql :: Maybe
  (EntityDef -> NonEmpty (FieldNameHS, FieldNameDB) -> Text -> Text)
connUpsertSql = Maybe
  (EntityDef -> NonEmpty (FieldNameHS, FieldNameDB) -> Text -> Text)
forall a. Maybe a
Nothing
        , connInsertManySql :: Maybe (EntityDef -> [[PersistValue]] -> InsertSqlResult)
connInsertManySql = Maybe (EntityDef -> [[PersistValue]] -> InsertSqlResult)
forall a. Maybe a
Nothing
        , connVault :: Vault
connVault = Vault
Vault.empty
        , connHooks :: SqlBackendHooks
connHooks = SqlBackendHooks
emptySqlBackendHooks
        , connStmtMap :: StatementCache
connStmtMap = MkStatementCache -> StatementCache
mkStatementCache (MkStatementCache -> StatementCache)
-> MkStatementCache -> StatementCache
forall a b. (a -> b) -> a -> b
$ IORef (Map Text Statement) -> MkStatementCache
mkSimpleStatementCache IORef (Map Text Statement)
connStmtMap
        , IO ()
Text
[EntityDef]
-> (Text -> IO Statement)
-> EntityDef
-> IO (Either [Text] [(Bool, Text)])
(Int, Int) -> Text -> Text
Text -> IO Statement
Text -> Text
LogFunc
FieldNameDB -> Text
EntityDef -> Text
EntityDef -> [PersistValue] -> InsertSqlResult
(Text -> IO Statement) -> IO ()
(Text -> IO Statement) -> Maybe IsolationLevel -> IO ()
connPrepare :: Text -> IO Statement
connInsertSql :: EntityDef -> [PersistValue] -> InsertSqlResult
connClose :: IO ()
connMigrateSql :: [EntityDef]
-> (Text -> IO Statement)
-> EntityDef
-> IO (Either [Text] [(Bool, Text)])
connBegin :: (Text -> IO Statement) -> Maybe IsolationLevel -> IO ()
connCommit :: (Text -> IO Statement) -> IO ()
connRollback :: (Text -> IO Statement) -> IO ()
connEscapeFieldName :: FieldNameDB -> Text
connEscapeTableName :: EntityDef -> Text
connEscapeRawName :: Text -> Text
connNoLimit :: Text
connRDBMS :: Text
connLimitOffset :: (Int, Int) -> Text -> Text
connLogFunc :: LogFunc
connPrepare :: Text -> IO Statement
connInsertSql :: EntityDef -> [PersistValue] -> InsertSqlResult
connClose :: IO ()
connMigrateSql :: [EntityDef]
-> (Text -> IO Statement)
-> EntityDef
-> IO (Either [Text] [(Bool, Text)])
connBegin :: (Text -> IO Statement) -> Maybe IsolationLevel -> IO ()
connCommit :: (Text -> IO Statement) -> IO ()
connRollback :: (Text -> IO Statement) -> IO ()
connEscapeFieldName :: FieldNameDB -> Text
connEscapeTableName :: EntityDef -> Text
connEscapeRawName :: Text -> Text
connNoLimit :: Text
connRDBMS :: Text
connLimitOffset :: (Int, Int) -> Text -> Text
connLogFunc :: LogFunc
..
        }

instance HasPersistBackend SqlBackend where
    type BaseBackend SqlBackend = SqlBackend
    persistBackend :: SqlBackend -> BaseBackend SqlBackend
persistBackend = SqlBackend -> BaseBackend SqlBackend
SqlBackend -> SqlBackend
forall a. a -> a
id

instance IsPersistBackend SqlBackend where
    mkPersistBackend :: BaseBackend SqlBackend -> SqlBackend
mkPersistBackend = BaseBackend SqlBackend -> SqlBackend
SqlBackend -> SqlBackend
forall a. a -> a
id