{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}

-- | Intended for creating new backends.
module Database.Persist.Sql.Internal
    ( mkColumns
    , defaultAttribute
    , BackendSpecificOverrides(..)
    , emptyBackendSpecificOverrides
    ) where

import Control.Applicative ((<|>))
import Data.Monoid (mappend, mconcat)
import Data.Text (Text)
import qualified Data.Text as T

import Database.Persist.Quasi
import Database.Persist.Sql.Types
import Database.Persist.Types
import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)

-- | Record of functions to override the default behavior in 'mkColumns'.
-- It is recommended you initialize this with 'emptyBackendSpecificOverrides' and override the default values,
-- so that as new fields are added, your code still compiles.
--
-- @since 2.11
data BackendSpecificOverrides = BackendSpecificOverrides
    { BackendSpecificOverrides -> Maybe (DBName -> DBName -> DBName)
backendSpecificForeignKeyName :: Maybe (DBName -> DBName -> DBName)
    }

findMaybe :: (a -> Maybe b) -> [a] -> Maybe b
findMaybe :: (a -> Maybe b) -> [a] -> Maybe b
findMaybe a -> Maybe b
p = [b] -> Maybe b
forall a. [a] -> Maybe a
listToMaybe ([b] -> Maybe b) -> ([a] -> [b]) -> [a] -> Maybe b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> Maybe b) -> [a] -> [b]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe a -> Maybe b
p

-- | Creates an empty 'BackendSpecificOverrides' (i.e. use the default behavior; no overrides)
--
-- @since 2.11
emptyBackendSpecificOverrides :: BackendSpecificOverrides
emptyBackendSpecificOverrides :: BackendSpecificOverrides
emptyBackendSpecificOverrides = Maybe (DBName -> DBName -> DBName) -> BackendSpecificOverrides
BackendSpecificOverrides Maybe (DBName -> DBName -> DBName)
forall a. Maybe a
Nothing

defaultAttribute :: [FieldAttr] -> Maybe Text
defaultAttribute :: [FieldAttr] -> Maybe Text
defaultAttribute = (FieldAttr -> Maybe Text) -> [FieldAttr] -> Maybe Text
forall a b. (a -> Maybe b) -> [a] -> Maybe b
findMaybe ((FieldAttr -> Maybe Text) -> [FieldAttr] -> Maybe Text)
-> (FieldAttr -> Maybe Text) -> [FieldAttr] -> Maybe Text
forall a b. (a -> b) -> a -> b
$ \case
    FieldAttrDefault Text
x -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
x
    FieldAttr
_ -> Maybe Text
forall a. Maybe a
Nothing

-- | Create the list of columns for the given entity.
mkColumns
    :: [EntityDef]
    -> EntityDef
    -> BackendSpecificOverrides
    -> ([Column], [UniqueDef], [ForeignDef])
mkColumns :: [EntityDef]
-> EntityDef
-> BackendSpecificOverrides
-> ([Column], [UniqueDef], [ForeignDef])
mkColumns [EntityDef]
allDefs EntityDef
t BackendSpecificOverrides
overrides =
    ([Column]
cols, EntityDef -> [UniqueDef]
entityUniques EntityDef
t, EntityDef -> [ForeignDef]
entityForeigns EntityDef
t)
  where
    cols :: [Column]
    cols :: [Column]
cols = (FieldDef -> Column) -> [FieldDef] -> [Column]
forall a b. (a -> b) -> [a] -> [b]
map FieldDef -> Column
goId [FieldDef]
idCol [Column] -> [Column] -> [Column]
forall a. Monoid a => a -> a -> a
`mappend` (FieldDef -> Column) -> [FieldDef] -> [Column]
forall a b. (a -> b) -> [a] -> [b]
map FieldDef -> Column
go (EntityDef -> [FieldDef]
entityFields EntityDef
t)

    idCol :: [FieldDef]
    idCol :: [FieldDef]
idCol = case EntityDef -> Maybe CompositeDef
entityPrimary EntityDef
t of
        Just CompositeDef
_ -> []
        Maybe CompositeDef
Nothing -> [EntityDef -> FieldDef
entityId EntityDef
t]

    goId :: FieldDef -> Column
    goId :: FieldDef -> Column
goId FieldDef
fd =
        Column :: DBName
-> Bool
-> SqlType
-> Maybe Text
-> Maybe Text
-> Maybe DBName
-> Maybe Integer
-> Maybe ColumnReference
-> Column
Column
            { cName :: DBName
cName = FieldDef -> DBName
fieldDB FieldDef
fd
            , cNull :: Bool
cNull = Bool
False
            , cSqlType :: SqlType
cSqlType = FieldDef -> SqlType
fieldSqlType FieldDef
fd
            , cDefault :: Maybe Text
cDefault =
                case [FieldAttr] -> Maybe Text
defaultAttribute ([FieldAttr] -> Maybe Text) -> [FieldAttr] -> Maybe Text
forall a b. (a -> b) -> a -> b
$ FieldDef -> [FieldAttr]
fieldAttrs FieldDef
fd of
                    Maybe Text
Nothing ->
                        -- So this is not necessarily a problem...
                        -- because you can use eg `inserKey` to insert
                        -- a value into the database without ever asking
                        -- for a default attribute.
                        Maybe Text
forall a. Maybe a
Nothing
                        -- But we need to be able to say "Hey, if this is
                        -- an *auto generated ID column*, then I need to
                        -- specify that it has the default serial picking
                        -- behavior for whatever SQL backend this is using.
                        -- Because naturally MySQL, Postgres, MSSQL, etc
                        -- all do ths differently, sigh.
                        -- Really, this should be something like,
                        --
                        -- > data ColumnDefault
                        -- >     = Custom Text
                        -- >     | AutogenerateId
                        -- >     | NoDefault
                        --
                        -- where Autogenerated is determined by the
                        -- MkPersistSettings.
                    Just Text
def ->
                        Text -> Maybe Text
forall a. a -> Maybe a
Just Text
def

            , cGenerated :: Maybe Text
cGenerated = FieldDef -> Maybe Text
fieldGenerated FieldDef
fd
            , cDefaultConstraintName :: Maybe DBName
cDefaultConstraintName =  Maybe DBName
forall a. Maybe a
Nothing
            , cMaxLen :: Maybe Integer
cMaxLen = [FieldAttr] -> Maybe Integer
maxLen ([FieldAttr] -> Maybe Integer) -> [FieldAttr] -> Maybe Integer
forall a b. (a -> b) -> a -> b
$ FieldDef -> [FieldAttr]
fieldAttrs FieldDef
fd
            , cReference :: Maybe ColumnReference
cReference = FieldDef -> Maybe ColumnReference
mkColumnReference FieldDef
fd
            }

    tableName :: DBName
    tableName :: DBName
tableName = EntityDef -> DBName
entityDB EntityDef
t


    go :: FieldDef -> Column
    go :: FieldDef -> Column
go FieldDef
fd =
        Column :: DBName
-> Bool
-> SqlType
-> Maybe Text
-> Maybe Text
-> Maybe DBName
-> Maybe Integer
-> Maybe ColumnReference
-> Column
Column
            { cName :: DBName
cName = FieldDef -> DBName
fieldDB FieldDef
fd
            , cNull :: Bool
cNull = [FieldAttr] -> IsNullable
nullable (FieldDef -> [FieldAttr]
fieldAttrs FieldDef
fd) IsNullable -> IsNullable -> Bool
forall a. Eq a => a -> a -> Bool
/= IsNullable
NotNullable Bool -> Bool -> Bool
|| EntityDef -> Bool
entitySum EntityDef
t
            , cSqlType :: SqlType
cSqlType = FieldDef -> SqlType
fieldSqlType FieldDef
fd
            , cDefault :: Maybe Text
cDefault = [FieldAttr] -> Maybe Text
defaultAttribute ([FieldAttr] -> Maybe Text) -> [FieldAttr] -> Maybe Text
forall a b. (a -> b) -> a -> b
$ FieldDef -> [FieldAttr]
fieldAttrs FieldDef
fd
            , cGenerated :: Maybe Text
cGenerated = FieldDef -> Maybe Text
fieldGenerated FieldDef
fd
            , cDefaultConstraintName :: Maybe DBName
cDefaultConstraintName =  Maybe DBName
forall a. Maybe a
Nothing
            , cMaxLen :: Maybe Integer
cMaxLen = [FieldAttr] -> Maybe Integer
maxLen ([FieldAttr] -> Maybe Integer) -> [FieldAttr] -> Maybe Integer
forall a b. (a -> b) -> a -> b
$ FieldDef -> [FieldAttr]
fieldAttrs FieldDef
fd
            , cReference :: Maybe ColumnReference
cReference = FieldDef -> Maybe ColumnReference
mkColumnReference FieldDef
fd
            }

    maxLen :: [FieldAttr] -> Maybe Integer
    maxLen :: [FieldAttr] -> Maybe Integer
maxLen = (FieldAttr -> Maybe Integer) -> [FieldAttr] -> Maybe Integer
forall a b. (a -> Maybe b) -> [a] -> Maybe b
findMaybe ((FieldAttr -> Maybe Integer) -> [FieldAttr] -> Maybe Integer)
-> (FieldAttr -> Maybe Integer) -> [FieldAttr] -> Maybe Integer
forall a b. (a -> b) -> a -> b
$ \case
        FieldAttrMaxlen Integer
n -> Integer -> Maybe Integer
forall a. a -> Maybe a
Just Integer
n
        FieldAttr
_ -> Maybe Integer
forall a. Maybe a
Nothing

    refNameFn :: DBName -> DBName -> DBName
refNameFn = (DBName -> DBName -> DBName)
-> Maybe (DBName -> DBName -> DBName) -> DBName -> DBName -> DBName
forall a. a -> Maybe a -> a
fromMaybe DBName -> DBName -> DBName
refName (BackendSpecificOverrides -> Maybe (DBName -> DBName -> DBName)
backendSpecificForeignKeyName BackendSpecificOverrides
overrides)

    mkColumnReference :: FieldDef -> Maybe ColumnReference
    mkColumnReference :: FieldDef -> Maybe ColumnReference
mkColumnReference FieldDef
fd =
        ((DBName, DBName) -> ColumnReference)
-> Maybe (DBName, DBName) -> Maybe ColumnReference
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap
            (\(DBName
tName, DBName
cName) ->
                DBName -> DBName -> FieldCascade -> ColumnReference
ColumnReference DBName
tName DBName
cName (FieldCascade -> ColumnReference)
-> FieldCascade -> ColumnReference
forall a b. (a -> b) -> a -> b
$ FieldCascade -> FieldCascade
overrideNothings (FieldCascade -> FieldCascade) -> FieldCascade -> FieldCascade
forall a b. (a -> b) -> a -> b
$ FieldDef -> FieldCascade
fieldCascade FieldDef
fd
            )
        (Maybe (DBName, DBName) -> Maybe ColumnReference)
-> Maybe (DBName, DBName) -> Maybe ColumnReference
forall a b. (a -> b) -> a -> b
$ DBName -> ReferenceDef -> [FieldAttr] -> Maybe (DBName, DBName)
ref (FieldDef -> DBName
fieldDB FieldDef
fd) (FieldDef -> ReferenceDef
fieldReference FieldDef
fd) (FieldDef -> [FieldAttr]
fieldAttrs FieldDef
fd)

    -- a 'Nothing' in the definition means that the QQ migration doesn't
    -- specify behavior. the default is RESTRICT. setting this here
    -- explicitly makes migrations run smoother.
    overrideNothings :: FieldCascade -> FieldCascade
overrideNothings (FieldCascade { fcOnUpdate :: FieldCascade -> Maybe CascadeAction
fcOnUpdate = Maybe CascadeAction
upd, fcOnDelete :: FieldCascade -> Maybe CascadeAction
fcOnDelete = Maybe CascadeAction
del }) =
        FieldCascade :: Maybe CascadeAction -> Maybe CascadeAction -> FieldCascade
FieldCascade
            { fcOnUpdate :: Maybe CascadeAction
fcOnUpdate = Maybe CascadeAction
upd Maybe CascadeAction -> Maybe CascadeAction -> Maybe CascadeAction
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> CascadeAction -> Maybe CascadeAction
forall a. a -> Maybe a
Just CascadeAction
Restrict
            , fcOnDelete :: Maybe CascadeAction
fcOnDelete = Maybe CascadeAction
del Maybe CascadeAction -> Maybe CascadeAction -> Maybe CascadeAction
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> CascadeAction -> Maybe CascadeAction
forall a. a -> Maybe a
Just CascadeAction
Restrict
            }

    ref :: DBName
        -> ReferenceDef
        -> [FieldAttr]
        -> Maybe (DBName, DBName) -- table name, constraint name
    ref :: DBName -> ReferenceDef -> [FieldAttr] -> Maybe (DBName, DBName)
ref DBName
c ReferenceDef
fe []
        | ForeignRef HaskellName
f FieldType
_ <- ReferenceDef
fe =
            (DBName, DBName) -> Maybe (DBName, DBName)
forall a. a -> Maybe a
Just ([EntityDef] -> HaskellName -> DBName
resolveTableName [EntityDef]
allDefs HaskellName
f, DBName -> DBName -> DBName
refNameFn DBName
tableName DBName
c)
        | Bool
otherwise = Maybe (DBName, DBName)
forall a. Maybe a
Nothing
    ref DBName
_ ReferenceDef
_ (FieldAttr
FieldAttrNoreference:[FieldAttr]
_) = Maybe (DBName, DBName)
forall a. Maybe a
Nothing
    ref DBName
c ReferenceDef
fe (FieldAttr
a:[FieldAttr]
as) = case FieldAttr
a of
        FieldAttrReference Text
x -> do
            (DBName
_, DBName
constraintName) <- DBName -> ReferenceDef -> [FieldAttr] -> Maybe (DBName, DBName)
ref DBName
c ReferenceDef
fe [FieldAttr]
as
            (DBName, DBName) -> Maybe (DBName, DBName)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> DBName
DBName Text
x, DBName
constraintName)
        FieldAttrConstraint Text
x -> do
            (DBName
tableName_, DBName
_) <- DBName -> ReferenceDef -> [FieldAttr] -> Maybe (DBName, DBName)
ref DBName
c ReferenceDef
fe [FieldAttr]
as
            (DBName, DBName) -> Maybe (DBName, DBName)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (DBName
tableName_, Text -> DBName
DBName Text
x)
        FieldAttr
_ -> DBName -> ReferenceDef -> [FieldAttr] -> Maybe (DBName, DBName)
ref DBName
c ReferenceDef
fe [FieldAttr]
as

refName :: DBName -> DBName -> DBName
refName :: DBName -> DBName -> DBName
refName (DBName Text
table) (DBName Text
column) =
    Text -> DBName
DBName (Text -> DBName) -> Text -> DBName
forall a b. (a -> b) -> a -> b
$ [Text] -> Text
forall a. Monoid a => [a] -> a
Data.Monoid.mconcat [Text
table, Text
"_", Text
column, Text
"_fkey"]

resolveTableName :: [EntityDef] -> HaskellName -> DBName
resolveTableName :: [EntityDef] -> HaskellName -> DBName
resolveTableName [] (HaskellName Text
hn) = [Char] -> DBName
forall a. HasCallStack => [Char] -> a
error ([Char] -> DBName) -> [Char] -> DBName
forall a b. (a -> b) -> a -> b
$ [Char]
"Table not found: " [Char] -> [Char] -> [Char]
forall a. Monoid a => a -> a -> a
`Data.Monoid.mappend` Text -> [Char]
T.unpack Text
hn
resolveTableName (EntityDef
e:[EntityDef]
es) HaskellName
hn
    | EntityDef -> HaskellName
entityHaskell EntityDef
e HaskellName -> HaskellName -> Bool
forall a. Eq a => a -> a -> Bool
== HaskellName
hn = EntityDef -> DBName
entityDB EntityDef
e
    | Bool
otherwise = [EntityDef] -> HaskellName -> DBName
resolveTableName [EntityDef]
es HaskellName
hn