server: safely signal backend support for event triggers

Hooks up event trigger codecs from #7237. This required fixing a problem where some backend types implemented `defaultTriggerOnReplication` with `error` which caused the server to crash when evaluating those for default values in codecs. The changes here add a type family to `Backend` called `XEventTriggers` that signals backend support for event triggers, and changes the type of `defaultTriggerOnReplication` to from `TriggerOnReplication` to `Maybe (XEventTriggers b, TriggerOnReplication)` so that it can only be implemented with a `Just` value if `XEventTriggers b` is inhabited. This emulates some existing type families in `Backend`. (Thanks to @daniel-chambers for this suggestion!)

I used the implementation of `defaultTriggerOnReplication` as a signal for event triggers support to prune the Metadata API so that event trigger fields will not appear in the OpenAPI spec for backend types that do not support event triggers. The codec version of the API will also not emit or accept those fields for those backend types. I think I could use `Typeable` to test whether `XEventTriggers` is `Void` instead of testing whether `defaultTriggerOnReplication` is `Nothing`. But the codec implementation will crash anyway if `defaultTriggerOnReplication` is `Nothing`.

I checked to make sure that graphql-engine-pro still compiles.

Ticket: https://hasurahq.atlassian.net/browse/GDC-521

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7295
GitOrigin-RevId: 2b2dd44291513266107ca25cf330319bf53a8b66
This commit is contained in:
Jesse Hallett 2022-12-21 12:14:07 -05:00 committed by hasura-bot
parent 204ec89c61
commit f4fa960ec6
10 changed files with 43 additions and 26 deletions

View File

@ -44,6 +44,7 @@ instance Backend 'BigQuery where
type XComputedField 'BigQuery = XEnable
type XRelay 'BigQuery = XDisable
type XNodesAgg 'BigQuery = XEnable
type XEventTriggers 'BigQuery = XDisable
type XNestedInserts 'BigQuery = XDisable
type XStreamingSubscription 'BigQuery = XDisable
@ -113,4 +114,4 @@ instance Backend 'BigQuery where
-- BigQuery does not posses connection pooling
pure ()
defaultTriggerOnReplication = error "Event triggers are not supported for the BigQuery source."
defaultTriggerOnReplication = Nothing

View File

@ -74,6 +74,7 @@ instance Backend 'DataConnector where
type XComputedField 'DataConnector = XDisable
type XRelay 'DataConnector = XDisable
type XNodesAgg 'DataConnector = XEnable
type XEventTriggers 'DataConnector = XDisable
type XNestedInserts 'DataConnector = XDisable
type XStreamingSubscription 'DataConnector = XDisable
@ -159,7 +160,7 @@ instance Backend 'DataConnector where
-- Data connectors do not have concept of connection pools
pure ()
defaultTriggerOnReplication = error "Event triggers is not implemented for the data connector backend."
defaultTriggerOnReplication = Nothing
data CustomBooleanOperator a = CustomBooleanOperator
{ _cboName :: Text,

View File

@ -60,6 +60,7 @@ instance Backend 'MSSQL where
type XComputedField 'MSSQL = XDisable
type XRelay 'MSSQL = XDisable
type XNodesAgg 'MSSQL = XEnable
type XEventTriggers 'MSSQL = XEnable
type XNestedInserts 'MSSQL = XDisable
type XStreamingSubscription 'MSSQL = XDisable
@ -121,4 +122,4 @@ instance Backend 'MSSQL where
resizeSourcePools sourceConfig =
MSSQL.mssqlResizePools (MSSQL._mscExecCtx sourceConfig)
defaultTriggerOnReplication = TOREnableTrigger
defaultTriggerOnReplication = Just ((), TOREnableTrigger)

View File

@ -42,6 +42,7 @@ instance Backend 'MySQL where
type XRelay 'MySQL = Void
type XNodesAgg 'MySQL = XEnable
type ExtraTableMetadata 'MySQL = ()
type XEventTriggers 'MySQL = XDisable
type XNestedInserts 'MySQL = XDisable
type XStreamingSubscription 'MySQL = XDisable
@ -145,4 +146,4 @@ instance Backend 'MySQL where
-- Trim pool by destroying excess resources, if any
Pool.tryTrimPool pool
defaultTriggerOnReplication = error "Event triggers are not implemented for the MySQL source."
defaultTriggerOnReplication = Nothing

View File

@ -118,6 +118,7 @@ instance
type XComputedField ('Postgres pgKind) = XEnable
type XRelay ('Postgres pgKind) = XEnable
type XNodesAgg ('Postgres pgKind) = XEnable
type XEventTriggers ('Postgres pgKind) = XEnable
type XNestedInserts ('Postgres pgKind) = XEnable
type XStreamingSubscription ('Postgres pgKind) = XEnable
@ -153,4 +154,4 @@ instance
resizeSourcePools sourceConfig = Postgres._pecResizePools (Postgres._pscExecCtx sourceConfig)
defaultTriggerOnReplication = TORDisableTrigger
defaultTriggerOnReplication = Just ((), TORDisableTrigger)

View File

@ -137,7 +137,10 @@ instance Backend b => FromJSON (CreateEventTriggerQuery b) where
(Just _, Just _) -> fail "only one of webhook or webhook_from_env should be given"
_ -> fail "must provide webhook or webhook_from_env"
mapM_ checkEmptyCols [insert, update, delete]
triggerOnReplication <- o .:? "trigger_on_replication" .!= defaultTriggerOnReplication @b
defTOR <- case defaultTriggerOnReplication @b of
Just (_, dt) -> pure dt
Nothing -> fail "No default setting for trigger_on_replication is defined for backend type."
triggerOnReplication <- o .:? "trigger_on_replication" .!= defTOR
return $ CreateEventTriggerQuery sourceName name table insert update delete (Just enableManual) retryConf webhook webhookFromEnv headers replace requestTransform responseTransform cleanupConfig triggerOnReplication
where
checkEmptyCols spec =

View File

@ -311,6 +311,9 @@ class
type XRelay b :: Type
type XNodesAgg b :: Type
-- | Flag the availability of event triggers.
type XEventTriggers b :: Type
-- | Extension to flag the availability of object and array relationships in inserts (aka nested inserts).
type XNestedInserts b :: Type
@ -355,8 +358,10 @@ class
-- Resize source pools based on the count of server replicas
resizeSourcePools :: SourceConfig b -> ServerReplicas -> IO ()
-- Default behaviour of SQL triggers on logically replicated database
defaultTriggerOnReplication :: TriggerOnReplication
-- | Default behaviour of SQL triggers on logically replicated database.
-- Setting this to @Nothing@ will disable event trigger configuration in the
-- metadata.
defaultTriggerOnReplication :: Maybe (XEventTriggers b, TriggerOnReplication)
-- Prisms
$(makePrisms ''ComputedFieldReturnType)

View File

@ -37,7 +37,7 @@ module Hasura.RQL.Types.EventTrigger
)
where
import Autodocodec (HasCodec, codec, dimapCodec, disjointEitherCodec, listCodec, literalTextCodec, optionalField', optionalFieldWithDefault', requiredField')
import Autodocodec (HasCodec, codec, dimapCodec, disjointEitherCodec, listCodec, literalTextCodec, optionalField', optionalFieldWithDefault', optionalFieldWithOmittedDefault', requiredField')
import Autodocodec qualified as AC
import Data.Aeson
import Data.Aeson.Extended ((.=?))
@ -405,7 +405,11 @@ instance (Backend b) => HasCodec (EventTriggerConf b) where
<*> optionalField' "request_transform" AC..= etcRequestTransform
<*> optionalField' "response_transform" AC..= etcResponseTransform
<*> optionalField' "cleanup_config" AC..= etcCleanupConfig
<*> optionalFieldWithDefault' "trigger_on_replication" (defaultTriggerOnReplication @b) AC..= etcTriggerOnReplication
<*> triggerOnReplication
where
triggerOnReplication = case defaultTriggerOnReplication @b of
Just (_, defTOR) -> optionalFieldWithOmittedDefault' "trigger_on_replication" defTOR AC..= etcTriggerOnReplication
Nothing -> error "No default setting for trigger_on_replication is defined for backend type."
instance Backend b => FromJSON (EventTriggerConf b) where
parseJSON = withObject "EventTriggerConf" \o -> do
@ -418,7 +422,10 @@ instance Backend b => FromJSON (EventTriggerConf b) where
requestTransform <- o .:? "request_transform"
responseTransform <- o .:? "response_transform"
cleanupConfig <- o .:? "cleanup_config"
triggerOnReplication <- o .:? "trigger_on_replication" .!= defaultTriggerOnReplication @b
defTOR <- case defaultTriggerOnReplication @b of
Just (_, dt) -> pure dt
Nothing -> fail "No default setting for trigger_on_replication is defined for backend type."
triggerOnReplication <- o .:? "trigger_on_replication" .!= defTOR
return $ EventTriggerConf name definition webhook webhookFromEnv retryConf headers requestTransform responseTransform cleanupConfig triggerOnReplication
instance Backend b => ToJSON (EventTriggerConf b) where
@ -436,9 +443,9 @@ instance Backend b => ToJSON (EventTriggerConf b) where
"response_transform" .=? responseTransform,
"cleanup_config" .=? cleanupConfig,
"trigger_on_replication"
.=? if triggerOnReplication == defaultTriggerOnReplication @b
then Nothing
else Just triggerOnReplication
.=? case defaultTriggerOnReplication @b of
Just (_, defTOR) -> if triggerOnReplication == defTOR then Nothing else Just triggerOnReplication
Nothing -> Just triggerOnReplication
]
updateCleanupConfig :: Maybe AutoTriggerLogCleanupConfig -> EventTriggerConf b -> EventTriggerConf b

View File

@ -197,7 +197,6 @@ deriving instance (Backend b) => Eq (TableMetadata b)
instance (Backend b) => ToJSON (TableMetadata b) where
toJSON = genericToJSON hasuraJSON
-- TODO: Replace uses of placeholderCodecViaJSON with proper codecs
instance (Backend b) => HasCodec (TableMetadata b) where
codec =
CommentCodec "Representation of a table in metadata, 'tables.yaml' and 'metadata.json'" $
@ -214,16 +213,14 @@ instance (Backend b) => HasCodec (TableMetadata b) where
<*> optSortedList "select_permissions" _pdRole .== _tmSelectPermissions
<*> optSortedList "update_permissions" _pdRole .== _tmUpdatePermissions
<*> optSortedList "delete_permissions" _pdRole .== _tmDeletePermissions
<*> optSortedListViaJSON "event_triggers" etcName .== _tmEventTriggers
<*> eventTriggers
<*> optionalFieldOrNull' "apollo_federation_config" .== _tmApolloFederationConfig
where
optSortedListViaJSON ::
(Eq a, FromJSON a, ToJSON a, Hashable k, Ord k, T.ToTxt k) =>
Text ->
(a -> k) ->
ObjectCodec (InsOrdHashMap k a) (InsOrdHashMap k a)
optSortedListViaJSON name keyForElem =
AC.optionalFieldWithOmittedDefaultWith' name (sortedElemsCodecWith placeholderCodecViaJSON keyForElem) mempty
-- Some backends do not implement event triggers. In those cases we tailor
-- the codec to omit the @"event_triggers"@ field from the API.
eventTriggers = case defaultTriggerOnReplication @b of
Just _ -> optSortedList "event_triggers" etcName .== _tmEventTriggers
Nothing -> pure mempty
optSortedList ::
(HasCodec a, Eq a, Hashable k, Ord k, T.ToTxt k) =>

View File

@ -331,9 +331,9 @@ sourcesToOrdJSONList sources =
eventTriggerConfToOrdJSON :: forall b. Backend b => EventTriggerConf b -> AO.Value
eventTriggerConfToOrdJSON (EventTriggerConf name definition webhook webhookFromEnv retryConf headers reqTransform respTransform cleanupConfig triggerOnReplication) =
let triggerOnReplicationMaybe =
if triggerOnReplication == defaultTriggerOnReplication @b
then Nothing
else Just triggerOnReplication
case defaultTriggerOnReplication @b of
Just (_, defTOR) -> if triggerOnReplication == defTOR then Nothing else Just triggerOnReplication
Nothing -> Just triggerOnReplication
in AO.object $
[ ("name", AO.toOrdered name),
("definition", AO.toOrdered definition),