-- | Types and functions for interacting with and manipulating SQL enums represented by
-- /single-column tables/, __not__ native Postgres enum types. Native enum types in Postgres are
-- difficult to change, so we discourage their use, but we might add support for native enum types
-- in the future.
module Hasura.RQL.DDL.Schema.Enum
  ( -- * Re-exports from "Hasura.RQL.Types.Column"
    EnumReference (..),
    EnumValues,
    EnumValueInfo (..),
    EnumValue (..),

    -- * Loading table info
    resolveEnumReferences,
  )
where

import Data.HashMap.Strict qualified as M
import Data.HashMap.Strict.NonEmpty qualified as NEHashMap
import Data.Sequence qualified as Seq
import Data.Sequence.NonEmpty qualified as NESeq
import Hasura.Prelude
import Hasura.RQL.Types.Backend
import Hasura.RQL.Types.Column
import Hasura.RQL.Types.Table

-- | Given a map of enum tables, computes all enum references implied by the given set of foreign
-- keys. A foreign key constitutes an enum reference iff the following conditions hold:
--
--   1. The key only includes a single column.
--   2. The referenced column is the table’s primary key.
--   3. The referenced table is, in fact, an enum table.
resolveEnumReferences ::
  forall b.
  Backend b =>
  HashMap (TableName b) (PrimaryKey b (Column b), TableConfig b, EnumValues) ->
  HashSet (ForeignKey b) ->
  HashMap (Column b) (NonEmpty (EnumReference b))
resolveEnumReferences :: HashMap
  (TableName b) (PrimaryKey b (Column b), TableConfig b, EnumValues)
-> HashSet (ForeignKey b)
-> HashMap (Column b) (NonEmpty (EnumReference b))
resolveEnumReferences HashMap
  (TableName b) (PrimaryKey b (Column b), TableConfig b, EnumValues)
enumTables =
  (NonEmpty (EnumReference b)
 -> NonEmpty (EnumReference b) -> NonEmpty (EnumReference b))
-> [(Column b, NonEmpty (EnumReference b))]
-> HashMap (Column b) (NonEmpty (EnumReference b))
forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> [(k, v)] -> HashMap k v
M.fromListWith NonEmpty (EnumReference b)
-> NonEmpty (EnumReference b) -> NonEmpty (EnumReference b)
forall a. Semigroup a => a -> a -> a
(<>) ([(Column b, NonEmpty (EnumReference b))]
 -> HashMap (Column b) (NonEmpty (EnumReference b)))
-> (HashSet (ForeignKey b)
    -> [(Column b, NonEmpty (EnumReference b))])
-> HashSet (ForeignKey b)
-> HashMap (Column b) (NonEmpty (EnumReference b))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Column b, EnumReference b)
 -> (Column b, NonEmpty (EnumReference b)))
-> [(Column b, EnumReference b)]
-> [(Column b, NonEmpty (EnumReference b))]
forall a b. (a -> b) -> [a] -> [b]
map ((EnumReference b -> NonEmpty (EnumReference b))
-> (Column b, EnumReference b)
-> (Column b, NonEmpty (EnumReference b))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (EnumReference b -> [EnumReference b] -> NonEmpty (EnumReference b)
forall a. a -> [a] -> NonEmpty a
:| [])) ([(Column b, EnumReference b)]
 -> [(Column b, NonEmpty (EnumReference b))])
-> (HashSet (ForeignKey b) -> [(Column b, EnumReference b)])
-> HashSet (ForeignKey b)
-> [(Column b, NonEmpty (EnumReference b))]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ForeignKey b -> Maybe (Column b, EnumReference b))
-> [ForeignKey b] -> [(Column b, EnumReference b)]
forall (f :: * -> *) a b.
Filterable f =>
(a -> Maybe b) -> f a -> f b
mapMaybe ForeignKey b -> Maybe (Column b, EnumReference b)
resolveEnumReference ([ForeignKey b] -> [(Column b, EnumReference b)])
-> (HashSet (ForeignKey b) -> [ForeignKey b])
-> HashSet (ForeignKey b)
-> [(Column b, EnumReference b)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HashSet (ForeignKey b) -> [ForeignKey b]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList
  where
    resolveEnumReference :: ForeignKey b -> Maybe (Column b, EnumReference b)
    resolveEnumReference :: ForeignKey b -> Maybe (Column b, EnumReference b)
resolveEnumReference ForeignKey b
foreignKey = do
      [(Column b
localColumn, Column b
foreignColumn)] <- [(Column b, Column b)] -> Maybe [(Column b, Column b)]
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([(Column b, Column b)] -> Maybe [(Column b, Column b)])
-> [(Column b, Column b)] -> Maybe [(Column b, Column b)]
forall a b. (a -> b) -> a -> b
$ NEHashMap (Column b) (Column b) -> [(Column b, Column b)]
forall k v. NEHashMap k v -> [(k, v)]
NEHashMap.toList (ForeignKey b -> NEHashMap (Column b) (Column b)
forall (b :: BackendType).
ForeignKey b -> NEHashMap (Column b) (Column b)
_fkColumnMapping @b ForeignKey b
foreignKey)
      let foreignKeyTableName :: TableName b
foreignKeyTableName = ForeignKey b -> TableName b
forall (b :: BackendType). ForeignKey b -> TableName b
_fkForeignTable ForeignKey b
foreignKey
      (PrimaryKey b (Column b)
primaryKey, TableConfig b
tConfig, EnumValues
enumValues) <- TableName b
-> HashMap
     (TableName b) (PrimaryKey b (Column b), TableConfig b, EnumValues)
-> Maybe (PrimaryKey b (Column b), TableConfig b, EnumValues)
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
M.lookup TableName b
foreignKeyTableName HashMap
  (TableName b) (PrimaryKey b (Column b), TableConfig b, EnumValues)
enumTables
      let tableCustomName :: Maybe Name
tableCustomName = TableConfig b -> Maybe Name
forall (b :: BackendType). TableConfig b -> Maybe Name
_tcCustomName TableConfig b
tConfig
      Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (PrimaryKey b (Column b) -> NESeq (Column b)
forall (b :: BackendType) a. PrimaryKey b a -> NESeq a
_pkColumns PrimaryKey b (Column b)
primaryKey NESeq (Column b) -> NESeq (Column b) -> Bool
forall a. Eq a => a -> a -> Bool
== Column b
foreignColumn Column b -> Seq (Column b) -> NESeq (Column b)
forall a. a -> Seq a -> NESeq a
NESeq.:<|| Seq (Column b)
forall a. Seq a
Seq.Empty)
      (Column b, EnumReference b) -> Maybe (Column b, EnumReference b)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Column b
localColumn, TableName b -> EnumValues -> Maybe Name -> EnumReference b
forall (b :: BackendType).
TableName b -> EnumValues -> Maybe Name -> EnumReference b
EnumReference TableName b
foreignKeyTableName EnumValues
enumValues Maybe Name
tableCustomName)