-- | 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 HashMap
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.Table.Cache (ForeignKey (..), PrimaryKey (..), TableConfig (..))

-- | 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 :: forall (b :: BackendType).
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)
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
HashMap.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 a b. (a -> b) -> (Column b, a) -> (Column b, 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 a b. (a -> Maybe b) -> [a] -> [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 a. HashSet a -> [a]
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 a. a -> Maybe a
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 (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
HashMap.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 a. a -> Maybe a
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)