packages feed

groundhog-th-0.12: Database/Groundhog/TH/Settings.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Database.Groundhog.TH.Settings
  ( PersistDefinitions (..),
    THEntityDef (..),
    THEmbeddedDef (..),
    THPrimitiveDef (..),
    THConstructorDef (..),
    THFieldDef (..),
    THUniqueDef (..),
    THUniqueKeyDef (..),
    THAutoKeyDef (..),
    PSEntityDef (..),
    PSEmbeddedDef (..),
    PSPrimitiveDef (..),
    PSConstructorDef (..),
    PSFieldDef (..),
    PSUniqueDef (..),
    PSUniqueKeyDef (..),
    PSAutoKeyDef (..),
  )
where

import Control.Applicative
import Control.Monad (forM, mzero, when)
import Data.Aeson
import Data.Aeson.Types (Pair)
import qualified Data.Foldable as Fold
import qualified Data.HashMap.Strict as H
import Data.Maybe (catMaybes)
import Data.Text (Text)
import Database.Groundhog.Core (ReferenceActionType (..), UniqueType (..))
import Database.Groundhog.Generic (PSFieldDef (..))
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (Lift (..))

data PersistDefinitions = PersistDefinitions {psEntities :: [PSEntityDef], psEmbeddeds :: [PSEmbeddedDef], psPrimitives :: [PSPrimitiveDef]}
  deriving (Show, Lift)

-- data SomeData a = U1 { foo :: Int} | U2 { bar :: Maybe String, asc :: Int64, add :: a} | U3 deriving (Show, Eq)

data THEntityDef = THEntityDef
  { thDataName :: Name, -- SomeData
    thDbEntityName :: String, -- SQLSomeData
    thEntitySchema :: Maybe String,
    thAutoKey :: Maybe THAutoKeyDef,
    thUniqueKeys :: [THUniqueKeyDef],
#if MIN_VERSION_template_haskell(2, 17, 0)
    thTypeParams :: [TyVarBndr ()],
#else
    thTypeParams :: [TyVarBndr],
#endif
    thConstructors :: [THConstructorDef]
  }
  deriving (Eq, Show)

data THAutoKeyDef = THAutoKeyDef
  { thAutoKeyConstrName :: String,
    thAutoKeyIsDef :: Bool
  }
  deriving (Eq, Show)

data THEmbeddedDef = THEmbeddedDef
  { thEmbeddedName :: Name,
    thEmbeddedConstructorName :: Name,
    -- | It is used only to set polymorphic part of name of its container
    thDbEmbeddedName :: String,
#if MIN_VERSION_template_haskell(2, 17, 0)
    thEmbeddedTypeParams :: [TyVarBndr ()],
#else
    thEmbeddedTypeParams :: [TyVarBndr],
#endif
    thEmbeddedFields :: [THFieldDef]
  }
  deriving (Eq, Show)

data THPrimitiveDef = THPrimitiveDef
  { thPrimitiveName :: Name,
    -- | It is used only to set polymorphic part of name of its container
    thPrimitiveDbName :: String,
    -- | Name of a pair of functions converting the value to and from a type that is an instance of `PrimitivePersistField`
    thPrimitiveConverter :: Name
  }
  deriving (Eq, Show)

data THConstructorDef = THConstructorDef
  { thConstrName :: Name, -- U2
    thPhantomConstrName :: String, -- U2Constructor
    thDbConstrName :: String, -- SQLU2
    thDbAutoKeyName :: Maybe String, -- u2_id
    thConstrFields :: [THFieldDef],
    thConstrUniques :: [THUniqueDef]
  }
  deriving (Eq, Show)

data THFieldDef = THFieldDef
  { -- | name in the record, bar
    thFieldName :: String,
    -- | column name, SQLbar
    thDbFieldName :: String,
    -- | column type, inet, NUMERIC(5, 2), VARCHAR(50), etc.
    thDbTypeName :: Maybe String,
    -- | name of constructor in the Field GADT, BarField
    thExprName :: String,
    thFieldType :: Type,
    thEmbeddedDef :: Maybe [PSFieldDef String],
    -- | default value in the database
    thDefaultValue :: Maybe String,
    thReferenceParent :: Maybe (Maybe ((Maybe String, String), [String]), Maybe ReferenceActionType, Maybe ReferenceActionType),
    -- | name of a pair of functions
    thFieldConverter :: Maybe Name
  }
  deriving (Eq, Show)

data THUniqueDef = THUniqueDef
  { thUniqueName :: String,
    thUniqueType :: UniqueType,
    -- | Either name of field, i.e, thFieldName, or expression
    thUniqueFields :: [Either String String]
  }
  deriving (Eq, Show)

data THUniqueKeyDef = THUniqueKeyDef
  { thUniqueKeyName :: String,
    thUniqueKeyPhantomName :: String,
    thUniqueKeyConstrName :: String,
    -- | It is used only to set polymorphic part of name of its container
    -- | It should repeat fields from THUniqueDef but it may give different settings for them. It is done to allow foreign key fields to be different from parent fields of the entity. These fields are used for creating a the key constructor and instances for it. For example, it can have a default value, or even a different type (INT8 may reference INT4).
    thUniqueKeyDbName :: String,
    thUniqueKeyFields :: [THFieldDef],
    -- | If True, make it an instance of Embedded
    thUniqueKeyMakeEmbedded :: Bool,
    thUniqueKeyIsDef :: Bool
  }
  deriving (Eq, Show)

data PSEntityDef = PSEntityDef
  { psDataName :: String, -- SomeData
    psDbEntityName :: Maybe String, -- SQLSomeData
    psEntitySchema :: Maybe String,
    psAutoKey :: Maybe (Maybe PSAutoKeyDef), -- SomeDataKey. Nothing - default key. Just Nothing - no autokey. Just (Just _) - specify autokey settings
    psUniqueKeys :: Maybe [PSUniqueKeyDef],
    psConstructors :: Maybe [PSConstructorDef]
  }
  deriving (Eq, Show, Lift)

data PSEmbeddedDef = PSEmbeddedDef
  { psEmbeddedName :: String,
    -- | It is used only to set polymorphic part of name of its container
    psDbEmbeddedName :: Maybe String,
    psEmbeddedFields :: Maybe [PSFieldDef String]
  }
  deriving (Eq, Show, Lift)

data PSPrimitiveDef = PSPrimitiveDef
  { psPrimitiveName :: String,
    -- | It is used only to set polymorphic part of name of its container
    psPrimitiveDbName :: Maybe String,
    -- | Name of a pair of functions converting the value to and from a type that is an instance of `PrimitivePersistField`
    psPrimitiveConverter :: String
  }
  deriving (Eq, Show, Lift)

data PSConstructorDef = PSConstructorDef
  { psConstrName :: String, -- U2
    psPhantomConstrName :: Maybe String, -- U2Constructor
    psDbConstrName :: Maybe String, -- SQLU2
    psDbAutoKeyName :: Maybe String, -- u2_id
    psConstrFields :: Maybe [PSFieldDef String],
    psConstrUniques :: Maybe [PSUniqueDef]
  }
  deriving (Eq, Show, Lift)

data PSUniqueDef = PSUniqueDef
  { psUniqueName :: String,
    psUniqueType :: Maybe UniqueType,
    psUniqueFields :: [Either String String]
  }
  deriving (Eq, Show, Lift)

data PSUniqueKeyDef = PSUniqueKeyDef
  { psUniqueKeyName :: String,
    psUniqueKeyPhantomName :: Maybe String,
    psUniqueKeyConstrName :: Maybe String,
    psUniqueKeyDbName :: Maybe String,
    psUniqueKeyFields :: Maybe [PSFieldDef String],
    psUniqueKeyMakeEmbedded :: Maybe Bool,
    psUniqueKeyIsDef :: Maybe Bool
  }
  deriving (Eq, Show, Lift)

data PSAutoKeyDef = PSAutoKeyDef
  { psAutoKeyConstrName :: Maybe String,
    psAutoKeyIsDef :: Maybe Bool
  }
  deriving (Eq, Show, Lift)

deriving instance Lift a => Lift (PSFieldDef a)

deriving instance Lift UniqueType

deriving instance Lift ReferenceActionType

instance FromJSON PersistDefinitions where
  {- it allows omitting parts of the settings file. All these forms are possible:
        definitions:
          - entity:name
        ---
          - entity:name
        ---
          entity: name
  -}
  parseJSON value =
    case value of
      Object v -> do
        defs <- v .:? "definitions"
        case defs of
          Just (Array arr) -> Fold.foldrM go initial arr
          Nothing -> go value initial
          Just _ -> mzero
      Array arr -> Fold.foldrM go initial arr
      _ -> mzero
    where
      initial = PersistDefinitions [] [] []
      go obj p@PersistDefinitions {..} = flip (withObject "definition") obj $ \v -> case () of
        _ | H.member "entity" v -> (\x -> p {psEntities = x : psEntities}) <$> parseJSON obj
        _ | H.member "embedded" v -> (\x -> p {psEmbeddeds = x : psEmbeddeds}) <$> parseJSON obj
        _ | H.member "primitive" v -> (\x -> p {psPrimitives = x : psPrimitives}) <$> parseJSON obj
        _ -> fail $ "Invalid definition: " ++ show obj

instance FromJSON PSEntityDef where
  parseJSON = withObject "entity" $ \v ->
    PSEntityDef <$> v .: "entity" <*> v .:? "dbName" <*> v .:? "schema" <*> optional (v .: "autoKey") <*> v .:? "keys" <*> v .:? "constructors"

instance FromJSON PSEmbeddedDef where
  parseJSON = withObject "embedded" $ \v ->
    PSEmbeddedDef <$> v .: "embedded" <*> v .:? "dbName" <*> v .:? "fields"

instance FromJSON PSPrimitiveDef where
  parseJSON = withObject "primitive" $ \v -> do
    when (H.member "representation" v) $ fail $ "parseJSON: field 'representation' is deprecated. Use 'converter' instead: " ++ show v
    PSPrimitiveDef <$> v .: "primitive" <*> v .:? "dbName" <*> v .: "converter"

instance FromJSON PSConstructorDef where
  parseJSON = withObject "constructor" $ \v ->
    PSConstructorDef <$> v .: "name" <*> v .:? "phantomName" <*> v .:? "dbName" <*> v .:? "keyDbName" <*> v .:? "fields" <*> v .:? "uniques"

instance FromJSON PSUniqueDef where
  parseJSON = withObject "unique" $ \v -> do
    fields <- v .: "fields"
    fields' <- forM fields $ \case
      Object expr -> Right <$> expr .: "expr"
      field -> Left <$> parseJSON field
    PSUniqueDef <$> v .: "name" <*> v .:? "type" <*> pure fields'

instance FromJSON UniqueType where
  parseJSON o = do
    x <- parseJSON o
    let vals = [("constraint", UniqueConstraint), ("index", UniqueIndex), ("primary", UniquePrimary False)]
    case lookup x vals of
      Just a -> pure a
      Nothing -> fail $ "parseJSON: UniqueType expected " ++ show (map fst vals) ++ ", but got " ++ x

instance FromJSON ReferenceActionType where
  parseJSON o = do
    x <- parseJSON o
    let vals = [("no action", NoAction), ("restrict", Restrict), ("cascade", Cascade), ("set null", SetNull), ("set default", SetDefault)]
    case lookup x vals of
      Just a -> pure a
      Nothing -> fail $ "parseJSON: UniqueType expected " ++ show (map fst vals) ++ ", but got " ++ x

instance FromJSON (PSFieldDef String) where
  parseJSON = withObject "field" $ \v ->
    PSFieldDef <$> v .: "name" <*> v .:? "dbName" <*> v .:? "type" <*> v .:? "exprName" <*> v .:? "embeddedType" <*> v .:? "default" <*> mkRefSettings v <*> v .:? "converter"
    where
      mkRefSettings v = do
        ref <- v .:? "reference"
        (parent, onDel, onUpd) <- case ref of
          Just (Object r) -> (,,) <$> optional parentRef <*> r .:? "onDelete" <*> r .:? "onUpdate"
            where
              parentRef = (,) <$> ((,) <$> r .:? "schema" <*> r .: "table") <*> r .: "columns"
          _ -> pure (Nothing, Nothing, Nothing)
        -- this temporary solution uses onDelete and onUpdate both from inside reference object (preferred) and from field level (for compatibility)
        (onDel', onUpd') <- (,) <$> v .:? "onDelete" <*> v .:? "onUpdate"
        pure $ case (parent, onDel <|> onDel', onUpd <|> onUpd') of
          (Nothing, Nothing, Nothing) -> Nothing
          refSettings -> Just refSettings

instance FromJSON PSUniqueKeyDef where
  parseJSON = withObject "unique key" $ \v ->
    PSUniqueKeyDef <$> v .: "name" <*> v .:? "keyPhantom" <*> v .:? "constrName" <*> v .:? "dbName" <*> v .:? "fields" <*> v .:? "mkEmbedded" <*> v .:? "default"

instance FromJSON PSAutoKeyDef where
  parseJSON = withObject "autogenerated key" $ \v ->
    PSAutoKeyDef <$> v .:? "constrName" <*> v .:? "default"

(.=?) :: ToJSON a => Text -> Maybe a -> Maybe Pair
name .=? value = (name .=) <$> value

(.=:) :: ToJSON a => Text -> Maybe [a] -> Maybe Pair
name .=: value = case value of
  Just (_ : _) -> Just $ name .= value
  _ -> Nothing

instance ToJSON PSEntityDef where
  toJSON PSEntityDef {..} = object $ catMaybes [Just $ "entity" .= psDataName, "dbName" .=? psDbEntityName, "schema" .=? psEntitySchema, "autoKey" .=? psAutoKey, "keys" .=: psUniqueKeys, "constructors" .=: psConstructors]

instance ToJSON PSConstructorDef where
  toJSON PSConstructorDef {..} = object $ catMaybes [Just $ "name" .= psConstrName, "phantomName" .=? psPhantomConstrName, "dbName" .=? psDbConstrName, "keyDbName" .=? psDbAutoKeyName, "fields" .=: psConstrFields, "uniques" .=: psConstrUniques]

instance ToJSON PSUniqueDef where
  toJSON PSUniqueDef {..} = object $ catMaybes [Just $ "name" .= psUniqueName, "type" .=? psUniqueType, "fields" .=? fields]
    where
      fields =
        if null psUniqueFields
          then Nothing
          else Just $ map (either toJSON (\x -> object ["expr" .= x])) psUniqueFields

instance ToJSON UniqueType where
  toJSON a = toJSON $ case a of
    UniqueConstraint -> "constraint" :: String
    UniqueIndex -> "index"
    UniquePrimary _ -> "primary"

instance ToJSON ReferenceActionType where
  toJSON a = toJSON $ case a of
    NoAction -> "no action" :: String
    Restrict -> "restrict"
    Cascade -> "cascade"
    SetNull -> "set null"
    SetDefault -> "set default"

instance ToJSON (PSFieldDef String) where
  toJSON PSFieldDef {..} = object $ catMaybes [Just $ "name" .= psFieldName, "dbName" .=? psDbFieldName, "type" .=? psDbTypeName, "exprName" .=? psExprName, "embeddedType" .=: psEmbeddedDef, "default" .=? psDefaultValue, "reference" .=? (psReferenceParent >>= mkRefSettings)]
    where
      mkRefSettings (parent, onDel, onUpd) = if null fields then Nothing else Just $ object fields
        where
          fields = catMaybes $ parent' ++ ["onDelete" .=? onDel, "onUpdate" .=? onUpd]
          parent' = case parent of
            Nothing -> []
            Just ((schema, table), columns) -> ["schema" .=? schema, Just $ "table" .= table, Just $ "columns" .= columns]

instance ToJSON PSUniqueKeyDef where
  toJSON PSUniqueKeyDef {..} = object $ catMaybes [Just $ "name" .= psUniqueKeyName, "keyPhantom" .=? psUniqueKeyPhantomName, "constrName" .=? psUniqueKeyConstrName, "dbName" .=? psUniqueKeyDbName, "mkEmbedded" .=? psUniqueKeyMakeEmbedded, "default" .=? psUniqueKeyIsDef, "fields" .=: psUniqueKeyFields]

instance ToJSON PSAutoKeyDef where
  toJSON PSAutoKeyDef {..} = object $ catMaybes ["constrName" .=? psAutoKeyConstrName, "default" .=? psAutoKeyIsDef]