graphql-engine/server/src-lib/Hasura/GraphQL/Schema/Action.hs
Tirumarai Selvan c0d2bc6653
Remote Joins: Create relationships across database and remote schemas (#2392)
add remote joins: Create relationships across database and remote schemas (#2392)

Co-authored-by: Aleksandra Sikora <ola.zxcvbnm@gmail.com>

Co-authored-by: Chris Done <chrisdone@gmail.com>
Co-authored-by: Chris Done <github@chrisdone.com>
Co-authored-by: wawhal <rishichandra.wawhal@gmail.com>
Co-authored-by: Aravind Shankar <aravind@hasura.io>
Co-authored-by: Brandon Simmons <brandon.m.simmons@gmail.com>
Co-authored-by: Rishichandra Wawhal <rishi@hasura.io>
Co-authored-by: Brandon Simmons <brandon@hasura.io>
Co-authored-by: nizar-m <19857260+nizar-m@users.noreply.github.com>
Co-authored-by: Praveen Durairaju <praveend.web@gmail.com>
Co-authored-by: rakeshkky <12475069+rakeshkky@users.noreply.github.com>
Co-authored-by: Anon Ray <rayanon004@gmail.com>
Co-authored-by: Shahidh K Muhammed <shahidh@hasura.io>
Co-authored-by: soorajshankar <soorajshankar@users.noreply.github.com>
Co-authored-by: Sooraj Sanker <sooraj@Soorajs-MacBook-Pro.local>
Co-authored-by: Karthikeyan Chinnakonda <karthikeyan@hasura.io>
Co-authored-by: Aleksandra Sikora <ola.zxcvbnm@gmail.com>
2020-05-27 20:32:58 +05:30

380 lines
13 KiB
Haskell

module Hasura.GraphQL.Schema.Action
( mkActionsSchema
) where
import qualified Data.HashMap.Strict as Map
import qualified Data.HashSet as Set
import qualified Language.GraphQL.Draft.Syntax as G
import Hasura.GraphQL.Schema.Builder
import Hasura.GraphQL.Schema.Common (mkDescriptionWith)
import Hasura.GraphQL.Resolve.Types
import Hasura.GraphQL.Validate.Types
import Hasura.Prelude
import Hasura.RQL.Types
import Hasura.Session
import Hasura.SQL.Types
mkAsyncActionSelectionType :: ActionName -> G.NamedType
mkAsyncActionSelectionType = G.NamedType . unActionName
mkAsyncActionQueryResponseObj
:: ActionName
-- Name of the action
-> GraphQLType
-- output type
-> ObjTyInfo
mkAsyncActionQueryResponseObj actionName outputType =
mkHsraObjTyInfo
(Just description)
(mkAsyncActionSelectionType actionName) -- "(action_name)"
mempty -- no arguments
(mapFromL _fiName fieldDefinitions)
where
description = G.Description $ "fields of action: " <>> actionName
mkFieldDefinition (fieldName, fieldDescription, fieldType) =
mkHsraObjFldInfo
(Just fieldDescription)
fieldName
mempty
fieldType
fieldDefinitions = map mkFieldDefinition
[ ( "id", "the unique id of an action"
, G.toGT $ mkScalarTy PGUUID)
, ( "created_at", "the time at which this action was created"
, G.toGT $ mkScalarTy PGTimeStampTZ)
, ( "errors", "errors related to the invocation"
, G.toGT $ mkScalarTy PGJSON)
, ( "output", "the output fields of this action"
, unGraphQLType outputType)
]
mkQueryActionField
:: ActionName
-> ActionInfo
-> [(PGCol, PGScalarType)]
-> (ActionExecutionContext, ObjFldInfo)
mkQueryActionField actionName actionInfo definitionList =
( actionExecutionContext
, fieldInfo
)
where
definition = _aiDefinition actionInfo
actionExecutionContext =
ActionExecutionContext
actionName
(_adOutputType definition)
(getActionOutputFields $ _aiOutputObject actionInfo)
definitionList
(_adHandler definition)
(_adHeaders definition)
(_adForwardClientHeaders definition)
description = mkDescriptionWith (PGDescription <$> _aiComment actionInfo) $
"perform the action: " <>> actionName
fieldInfo =
mkHsraObjFldInfo
(Just description)
(unActionName actionName)
(mapFromL _iviName $ map mkActionArgument $ _adArguments definition)
actionFieldResponseType
mkActionArgument argument =
InpValInfo (_argDescription argument) (unArgumentName $ _argName argument)
Nothing $ unGraphQLType $ _argType argument
actionFieldResponseType = unGraphQLType $ _adOutputType definition
mkMutationActionField
:: ActionName
-> ActionInfo
-> [(PGCol, PGScalarType)]
-> ActionMutationKind
-> (ActionMutationExecutionContext, ObjFldInfo)
mkMutationActionField actionName actionInfo definitionList kind =
( actionExecutionContext
, fieldInfo
)
where
definition = _aiDefinition actionInfo
actionExecutionContext =
case kind of
ActionSynchronous ->
ActionMutationSyncWebhook $ ActionExecutionContext actionName
(_adOutputType definition)
(getActionOutputFields $ _aiOutputObject actionInfo)
definitionList
(_adHandler definition)
(_adHeaders definition)
(_adForwardClientHeaders definition)
ActionAsynchronous -> ActionMutationAsync
description = mkDescriptionWith (PGDescription <$> _aiComment actionInfo) $
"perform the action: " <>> actionName
fieldInfo =
mkHsraObjFldInfo
(Just description)
(unActionName actionName)
(mapFromL _iviName $ map mkActionArgument $ _adArguments definition)
actionFieldResponseType
mkActionArgument argument =
InpValInfo (_argDescription argument) (unArgumentName $ _argName argument)
Nothing $ unGraphQLType $ _argType argument
actionFieldResponseType =
case kind of
ActionSynchronous -> unGraphQLType $ _adOutputType definition
ActionAsynchronous -> G.toGT $ G.toNT $ mkScalarTy PGUUID
mkQueryField
:: ActionName
-> Maybe Text
-> ResolvedActionDefinition
-> [(PGCol, PGScalarType)]
-> ActionMutationKind
-> Maybe (ActionSelectOpContext, ObjFldInfo, TypeInfo)
mkQueryField actionName comment definition definitionList kind =
case kind of
ActionAsynchronous ->
Just ( ActionSelectOpContext (_adOutputType definition) definitionList
, mkHsraObjFldInfo (Just description) (unActionName actionName)
(mapFromL _iviName [idArgument])
(G.toGT $ G.toGT $ mkAsyncActionSelectionType actionName)
, TIObj $ mkAsyncActionQueryResponseObj actionName $
_adOutputType definition
)
ActionSynchronous -> Nothing
where
description = mkDescriptionWith (PGDescription <$> comment) $
"retrieve the result of action: " <>> actionName
idArgument =
InpValInfo (Just idDescription) "id" Nothing $ G.toNT $ mkScalarTy PGUUID
where
idDescription = G.Description $ "id of the action: " <>> actionName
mkPGFieldType
:: ObjectFieldName
-> (G.GType, OutputFieldTypeInfo)
-> HashMap ObjectFieldName PGColumnInfo
-> PGScalarType
mkPGFieldType fieldName (fieldType, fieldTypeInfo) fieldReferences =
case (G.isListType fieldType, fieldTypeInfo) of
-- for scalar lists, we treat them as json columns
(True, _) -> PGJSON
-- enums the same
(False, OutputFieldEnum _) -> PGJSON
-- default to PGJSON unless you have to join with a postgres table
-- i.e, if this field is specified as part of some relationship's
-- mapping, we can cast this column's value as the remote column's type
(False, OutputFieldScalar _) ->
case Map.lookup fieldName fieldReferences of
Just columnInfo -> unsafePGColumnToRepresentation $ pgiType columnInfo
Nothing -> PGJSON
mkDefinitionList :: AnnotatedObjectType -> HashMap ObjectFieldName PGColumnInfo -> [(PGCol, PGScalarType)]
mkDefinitionList annotatedOutputType fieldReferences =
[ (unsafePGCol $ coerce k, mkPGFieldType k v fieldReferences)
| (k, v) <- Map.toList $ _aotAnnotatedFields annotatedOutputType
]
mkFieldMap
:: AnnotatedObjectType
-> ActionInfo
-> HashMap ObjectFieldName PGColumnInfo
-> RoleName
-> HashMap (G.NamedType,G.Name) ResolveField
mkFieldMap annotatedOutputType actionInfo fieldReferences roleName =
Map.fromList $ fields <> catMaybes relationships
where
fields =
flip map (Map.toList $ _aotAnnotatedFields annotatedOutputType) $
\(fieldName, (fieldType, fieldTypeInfo)) ->
( (actionOutputBaseType, unObjectFieldName fieldName)
, RFPGColumn $ PGColumnInfo
(unsafePGCol $ coerce fieldName)
(coerce fieldName)
0
(PGColumnScalar $ mkPGFieldType fieldName (fieldType, fieldTypeInfo) fieldReferences)
(G.isNullable fieldType)
Nothing
)
relationships =
flip map (Map.toList $ _aotRelationships annotatedOutputType) $
\(relationshipName, relationship) ->
let remoteTableInfo = _trRemoteTable relationship
remoteTable = _tciName $ _tiCoreInfo remoteTableInfo
filterAndLimitM = getFilterAndLimit remoteTableInfo
columnMapping = Map.fromList $
[ (unsafePGCol $ coerce k, pgiColumn v)
| (k, v) <- Map.toList $ _trFieldMapping relationship
]
in case filterAndLimitM of
Just (tableFilter, tableLimit) ->
Just ( ( actionOutputBaseType
, unRelationshipName relationshipName
)
, RFRelationship $ RelationshipField
(RelInfo
-- RelationshipName, which is newtype wrapper over G.Name is always
-- non-empty text so as to conform GraphQL spec
(RelName $ mkNonEmptyTextUnsafe $ coerce relationshipName)
(_trType relationship)
columnMapping remoteTable True)
False mempty
tableFilter
tableLimit
)
Nothing -> Nothing
getFilterAndLimit remoteTableInfo =
if roleName == adminRoleName
then Just (annBoolExpTrue, Nothing)
else do
selectPermisisonInfo <-
getSelectPermissionInfoM remoteTableInfo roleName
return (spiFilter selectPermisisonInfo, spiLimit selectPermisisonInfo)
actionOutputBaseType =
G.getBaseType $ unGraphQLType $ _adOutputType $ _aiDefinition actionInfo
mkFieldReferences :: AnnotatedObjectType -> HashMap ObjectFieldName PGColumnInfo
mkFieldReferences annotatedOutputType=
Map.unions $ map _trFieldMapping $ Map.elems $
_aotRelationships annotatedOutputType
mkMutationActionFieldsAndTypes
:: ActionInfo
-> ActionPermissionInfo
-> ActionMutationKind
-> ( Maybe (ActionSelectOpContext, ObjFldInfo, TypeInfo)
-- context, field, response type info
, (ActionMutationExecutionContext, ObjFldInfo) -- mutation field
, FieldMap
)
mkMutationActionFieldsAndTypes actionInfo permission kind =
( mkQueryField actionName comment definition definitionList kind
, mkMutationActionField actionName actionInfo definitionList kind
, fieldMap
)
where
actionName = _aiName actionInfo
annotatedOutputType = _aiOutputObject actionInfo
definition = _aiDefinition actionInfo
roleName = _apiRole permission
comment = _aiComment actionInfo
-- all the possible field references
fieldReferences = mkFieldReferences annotatedOutputType
definitionList = mkDefinitionList annotatedOutputType fieldReferences
fieldMap = mkFieldMap annotatedOutputType actionInfo fieldReferences roleName
mkQueryActionFieldsAndTypes
:: ActionInfo
-> ActionPermissionInfo
-> ((ActionExecutionContext, ObjFldInfo)
, FieldMap
)
mkQueryActionFieldsAndTypes actionInfo permission =
( mkQueryActionField actionName actionInfo definitionList
, fieldMap
)
where
actionName = _aiName actionInfo
roleName = _apiRole permission
annotatedOutputType = _aiOutputObject actionInfo
fieldReferences = mkFieldReferences annotatedOutputType
definitionList = mkDefinitionList annotatedOutputType fieldReferences
fieldMap = mkFieldMap annotatedOutputType actionInfo fieldReferences roleName
mkMutationActionSchemaOne
:: ActionInfo
-> ActionMutationKind
-> Map.HashMap RoleName
( Maybe (ActionSelectOpContext, ObjFldInfo, TypeInfo)
, (ActionMutationExecutionContext, ObjFldInfo)
, FieldMap
)
mkMutationActionSchemaOne actionInfo kind =
flip Map.map permissions $ \permission ->
mkMutationActionFieldsAndTypes actionInfo permission kind
where
adminPermission = ActionPermissionInfo adminRoleName
permissions = Map.insert adminRoleName adminPermission $ _aiPermissions actionInfo
mkQueryActionSchemaOne
:: ActionInfo
-> Map.HashMap RoleName
( (ActionExecutionContext, ObjFldInfo)
, FieldMap
)
mkQueryActionSchemaOne actionInfo =
flip Map.map permissions $ \permission ->
mkQueryActionFieldsAndTypes actionInfo permission
where
adminPermission = ActionPermissionInfo adminRoleName
permissions = Map.insert adminRoleName adminPermission $ _aiPermissions actionInfo
mkActionsSchema
:: ActionCache
-> Map.HashMap RoleName (RootFields, TyAgg)
mkActionsSchema =
foldl'
(\aggregate actionInfo ->
case _adType $ _aiDefinition actionInfo of
ActionQuery ->
Map.foldrWithKey (accumulateQuery (_aiPgScalars actionInfo)) aggregate $ mkQueryActionSchemaOne actionInfo
ActionMutation kind ->
Map.foldrWithKey (accumulateMutation (_aiPgScalars actionInfo)) aggregate $ mkMutationActionSchemaOne actionInfo kind
)
mempty
where
-- we'll need to add uuid and timestamptz for actions
mkNewRoleState pgScalars =
( mempty
, foldr addScalarToTyAgg mempty $
pgScalars <> Set.fromList [PGJSON, PGTimeStampTZ, PGUUID]
)
accumulateQuery pgScalars roleName (actionField, fields) =
Map.alter (Just . addToStateSync . fromMaybe (mkNewRoleState pgScalars)) roleName
where
addToStateSync (rootFields, tyAgg) =
( addQueryField (first QCAction actionField) rootFields
, addFieldsToTyAgg fields tyAgg
)
accumulateMutation pgScalars roleName (queryFieldM, actionField, fields) =
Map.alter (Just . addToState . fromMaybe (mkNewRoleState pgScalars)) roleName
where
addToState = case queryFieldM of
Just (fldCtx, fldDefinition, responseTypeInfo) ->
addToStateAsync (fldCtx, fldDefinition) responseTypeInfo
Nothing -> addToStateSync
addToStateSync (rootFields, tyAgg) =
( addMutationField (first MCAction actionField) rootFields
, addFieldsToTyAgg fields tyAgg
)
addToStateAsync queryField responseTypeInfo (rootFields, tyAgg) =
( addMutationField (first MCAction actionField) $
addQueryField
(first QCAsyncActionFetch queryField)
rootFields
, addTypeInfoToTyAgg responseTypeInfo $
addFieldsToTyAgg fields tyAgg
)