graphql-engine/server/src-lib/Hasura/Backends/Postgres/Instances/Metadata.hs
Antoine Leblanc 04d8f068b6 Remove explicit case on the backend tag in Cache
### Description

As part of the cache building process, we create / update / migrate the catalog that each DB uses as a place to store event trigger information. The function that decides how this should be done was doing an explicit `case ... of` on the backend tag, instead of delegating to one of the backend classes. The downsides of this is that:
- it adds a "friction point" where the backend matters in the core of the engine, which is otherwise written to be almost entirely backend-agnostic
- it creates imports from deep in the engine to the `Backends`, which we try to restrict to a very small set of clearly identified files (the `Instances` files)
- it is currently implemented using a "catch all" default case, which might not always be correct for new backends

This PR makes the catalog updating process a part of `BackendMetadata`, and cleans the corresponding schema cache code.

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4457
GitOrigin-RevId: 592f0eaa97a7c38f4e6d4400e1d2353aab12c97e
2022-05-05 13:44:56 +00:00

133 lines
5.2 KiB
Haskell

{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | Postgres Instances Metadata
--
-- Defines a 'Hasura.RQL.Types.Metadata.Backend.BackendMetadata' type class instance for Postgres.
module Hasura.Backends.Postgres.Instances.Metadata () where
import Data.HashMap.Strict qualified as Map
import Data.Text.Extended
import Hasura.Backends.Postgres.DDL qualified as PG
import Hasura.Backends.Postgres.SQL.Types (QualifiedTable)
import Hasura.Backends.Postgres.Types.CitusExtraTableMetadata
import Hasura.Base.Error
import Hasura.Prelude
import Hasura.RQL.Types.Backend (Backend)
import Hasura.RQL.Types.Metadata.Backend
import Hasura.RQL.Types.Relationships.Local
import Hasura.RQL.Types.Table
import Hasura.SQL.Backend
--------------------------------------------------------------------------------
-- PostgresMetadata
-- | We differentiate the handling of metadata between Citus and Vanilla
-- Postgres because Citus imposes limitations on the types of joins that it
-- permits, which then limits the types of relations that we can track.
class PostgresMetadata (pgKind :: PostgresKind) where
-- TODO: find a better name
validateRel ::
MonadError QErr m =>
TableCache ('Postgres pgKind) ->
QualifiedTable ->
Either (ObjRelDef ('Postgres pgKind)) (ArrRelDef ('Postgres pgKind)) ->
m ()
instance PostgresMetadata 'Vanilla where
validateRel _ _ _ = pure ()
instance PostgresMetadata 'Citus where
validateRel ::
forall m.
MonadError QErr m =>
TableCache ('Postgres 'Citus) ->
QualifiedTable ->
Either (ObjRelDef ('Postgres 'Citus)) (ArrRelDef ('Postgres 'Citus)) ->
m ()
validateRel tableCache sourceTable relInfo = do
sourceTableInfo <- lookupTableInfo sourceTable
case relInfo of
Left (RelDef _ obj _) ->
case obj of
RUFKeyOn (SameTable _) -> pure ()
RUFKeyOn (RemoteTable targetTable _) -> checkObjectRelationship sourceTableInfo targetTable
RUManual RelManualConfig {} -> pure ()
Right (RelDef _ obj _) ->
case obj of
RUFKeyOn (ArrRelUsingFKeyOn targetTable _col) -> checkArrayRelationship sourceTableInfo targetTable
RUManual RelManualConfig {} -> pure ()
where
lookupTableInfo tableName =
Map.lookup tableName tableCache
`onNothing` throw400 NotFound ("no such table " <>> tableName)
checkObjectRelationship sourceTableInfo targetTable = do
targetTableInfo <- lookupTableInfo targetTable
let notSupported = throwNotSupportedError sourceTableInfo targetTableInfo "object"
case ( _tciExtraTableMetadata $ _tiCoreInfo sourceTableInfo,
_tciExtraTableMetadata $ _tiCoreInfo targetTableInfo
) of
(Distributed {}, Local) -> notSupported
(Distributed {}, Reference) -> pure ()
(Distributed {}, Distributed {}) -> pure ()
(_, Distributed {}) -> notSupported
(_, _) -> pure ()
checkArrayRelationship sourceTableInfo targetTable = do
targetTableInfo <- lookupTableInfo targetTable
let notSupported = throwNotSupportedError sourceTableInfo targetTableInfo "array"
case ( _tciExtraTableMetadata $ _tiCoreInfo sourceTableInfo,
_tciExtraTableMetadata $ _tiCoreInfo targetTableInfo
) of
(Distributed {}, Distributed {}) -> pure ()
(Distributed {}, _) -> notSupported
(_, Distributed {}) -> notSupported
(_, _) -> pure ()
showDistributionType :: ExtraTableMetadata -> Text
showDistributionType = \case
Local -> "local"
Distributed _ -> "distributed"
Reference -> "reference"
throwNotSupportedError :: TableInfo ('Postgres 'Citus) -> TableInfo ('Postgres 'Citus) -> Text -> m ()
throwNotSupportedError sourceTableInfo targetTableInfo t =
let tciSrc = _tiCoreInfo sourceTableInfo
tciTgt = _tiCoreInfo targetTableInfo
in throw400
NotSupported
( showDistributionType (_tciExtraTableMetadata tciSrc)
<> " tables ("
<> toTxt (_tciName tciSrc)
<> ") cannot have an "
<> t
<> " relationship against a "
<> showDistributionType (_tciExtraTableMetadata $ _tiCoreInfo targetTableInfo)
<> " table ("
<> toTxt (_tciName tciTgt)
<> ")"
)
----------------------------------------------------------------
-- BackendMetadata instance
instance
( Backend ('Postgres pgKind),
PostgresMetadata pgKind,
PG.ToMetadataFetchQuery pgKind
) =>
BackendMetadata ('Postgres pgKind)
where
prepareCatalog = PG.prepareCatalog
buildComputedFieldInfo = PG.buildComputedFieldInfo
fetchAndValidateEnumValues = PG.fetchAndValidateEnumValues
resolveSourceConfig = PG.resolveSourceConfig
resolveDatabaseMetadata = PG.resolveDatabaseMetadata
parseBoolExpOperations = PG.parseBoolExpOperations
buildFunctionInfo = PG.buildFunctionInfo
updateColumnInEventTrigger = PG.updateColumnInEventTrigger
parseCollectableType = PG.parseCollectableType
postDropSourceHook = PG.postDropSourceHook
validateRelationship = validateRel @pgKind