graphql-engine/server/src-lib/Hasura/GraphQL/Schema/Mutation/Insert.hs
Vamshi Surabhi b84db36ebb
allow custom mutations through actions (#3042)
* basic doc for actions

* custom_types, sync and async actions

* switch to graphql-parser-hs on github

* update docs

* metadata import/export

* webhook calls are now supported

* relationships in sync actions

* initialise.sql is now in sync with the migration file

* fix metadata tests

* allow specifying arguments of actions

* fix blacklist check on check_build_worthiness job

* track custom_types and actions related tables

* handlers are now triggered on async actions

* default to pgjson unless a field is involved in relationships, for generating definition list

* use 'true' for action filter for non admin role

* fix create_action_permission sql query

* drop permissions when dropping an action

* add a hdb_role view (and relationships) to fetch all roles in the system

* rename 'webhook' key in action definition to 'handler'

* allow templating actions wehook URLs with env vars

* add 'update_action' /v1/query type

* allow forwarding client headers by setting `forward_client_headers` in action definition

* add 'headers' configuration in action definition

* handle webhook error response based on status codes

* support array relationships for custom types

* implement single row mutation, see https://github.com/hasura/graphql-engine/issues/3731

* single row mutation: rename 'pk_columns' -> 'columns' and no-op refactor

* use top level primary key inputs for delete_by_pk & account select permissions for single row mutations

* use only REST semantics to resolve the webhook response

* use 'pk_columns' instead of 'columns' for update_by_pk input

* add python basic tests for single row mutations

* add action context (name) in webhook payload

* Async action response is accessible for non admin roles only if
  the request session vars equals to action's

* clean nulls, empty arrays for actions, custom types in export metadata

* async action mutation returns only the UUID of the action

* unit tests for URL template parser

* Basic sync actions python tests

* fix output in async query & add async tests

* add admin secret header in async actions python test

* document async action architecture in Resolve/Action.hs file

* support actions returning array of objects

* tests for list type response actions

* update docs with actions and custom types metadata API reference

* update actions python tests as per #f8e1330

Co-authored-by: Tirumarai Selvan <tirumarai.selvan@gmail.com>
Co-authored-by: Aravind Shankar <face11301@gmail.com>
Co-authored-by: Rakesh Emmadi <12475069+rakeshkky@users.noreply.github.com>
2020-02-13 23:08:23 +05:30

237 lines
7.1 KiB
Haskell

module Hasura.GraphQL.Schema.Mutation.Insert
( mkInsInp
, mkInsInpTy
, mkRelInsInps
, mkInsMutFld
, mkInsertOneMutationField
, mkOnConflictTypes
) where
import qualified Data.HashMap.Strict as Map
import qualified Language.GraphQL.Draft.Syntax as G
import Hasura.GraphQL.Resolve.Types
import Hasura.GraphQL.Schema.BoolExp
import Hasura.GraphQL.Schema.Common
import Hasura.GraphQL.Schema.Mutation.Common
import Hasura.GraphQL.Validate.Types
import Hasura.Prelude
import Hasura.RQL.Types
import Hasura.SQL.Types
-- table_insert_input
mkInsInpTy :: QualifiedTable -> G.NamedType
mkInsInpTy tn =
G.NamedType $ qualObjectToName tn <> "_insert_input"
-- table_obj_rel_insert_input
mkObjInsInpTy :: QualifiedTable -> G.NamedType
mkObjInsInpTy tn =
G.NamedType $ qualObjectToName tn <> "_obj_rel_insert_input"
-- table_arr_rel_insert_input
mkArrInsInpTy :: QualifiedTable -> G.NamedType
mkArrInsInpTy tn =
G.NamedType $ qualObjectToName tn <> "_arr_rel_insert_input"
-- table_on_conflict
mkOnConflictInpTy :: QualifiedTable -> G.NamedType
mkOnConflictInpTy tn =
G.NamedType $ qualObjectToName tn <> "_on_conflict"
-- table_constraint
mkConstraintInpTy :: QualifiedTable -> G.NamedType
mkConstraintInpTy tn =
G.NamedType $ qualObjectToName tn <> "_constraint"
-- table_update_column
mkUpdColumnInpTy :: QualifiedTable -> G.NamedType
mkUpdColumnInpTy tn =
G.NamedType $ qualObjectToName tn <> "_update_column"
{-
input table_obj_rel_insert_input {
data: table_insert_input!
on_conflict: table_on_conflict
}
-}
{-
input table_arr_rel_insert_input {
data: [table_insert_input!]!
on_conflict: table_on_conflict
}
-}
mkRelInsInps
:: QualifiedTable -> Bool -> [InpObjTyInfo]
mkRelInsInps tn upsertAllowed = [objRelInsInp, arrRelInsInp]
where
onConflictInpVal =
InpValInfo Nothing "on_conflict" Nothing $ G.toGT $ mkOnConflictInpTy tn
onConflictInp = bool [] [onConflictInpVal] upsertAllowed
objRelDesc = G.Description $
"input type for inserting object relation for remote table " <>> tn
objRelDataInp = InpValInfo Nothing "data" Nothing $ G.toGT $
G.toNT $ mkInsInpTy tn
objRelInsInp = mkHsraInpTyInfo (Just objRelDesc) (mkObjInsInpTy tn)
$ fromInpValL $ objRelDataInp : onConflictInp
arrRelDesc = G.Description $
"input type for inserting array relation for remote table " <>> tn
arrRelDataInp = InpValInfo Nothing "data" Nothing $ G.toGT $
G.toNT $ G.toLT $ G.toNT $ mkInsInpTy tn
arrRelInsInp = mkHsraInpTyInfo (Just arrRelDesc) (mkArrInsInpTy tn)
$ fromInpValL $ arrRelDataInp : onConflictInp
{-
input table_insert_input {
col1: colty1
.
.
coln: coltyn
}
-}
mkInsInp
:: QualifiedTable -> [PGColumnInfo] -> RelationInfoMap -> InpObjTyInfo
mkInsInp tn insCols relInfoMap =
mkHsraInpTyInfo (Just desc) (mkInsInpTy tn) $ fromInpValL $
map mkPGColInp insCols <> relInps
where
desc = G.Description $
"input type for inserting data into table " <>> tn
relInps = flip map (Map.toList relInfoMap) $
\(relName, relInfo) ->
let remoteQT = riRTable relInfo
tyMaker = case riType relInfo of
ObjRel -> mkObjInsInpTy
ArrRel -> mkArrInsInpTy
in InpValInfo Nothing (mkRelName relName) Nothing $
G.toGT $ tyMaker remoteQT
{-
input table_on_conflict {
constraint: table_constraint!
update_columns: [table_column!]
where: table_bool_exp
}
-}
mkOnConflictInp :: QualifiedTable -> InpObjTyInfo
mkOnConflictInp tn =
mkHsraInpTyInfo (Just desc) (mkOnConflictInpTy tn) $ fromInpValL
[constraintInpVal, updateColumnsInpVal, whereInpVal]
where
desc = G.Description $
"on conflict condition type for table " <>> tn
constraintInpVal = InpValInfo Nothing (G.Name "constraint") Nothing $
G.toGT $ G.toNT $ mkConstraintInpTy tn
updateColumnsInpVal = InpValInfo Nothing (G.Name "update_columns") Nothing $
G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkUpdColumnInpTy tn
whereInpVal = InpValInfo Nothing (G.Name "where") Nothing $
G.toGT $ mkBoolExpTy tn
{-
insert_table(
objects: [table_insert_input!]!
on_conflict: table_on_conflict
): table_mutation_response!
-}
mkInsMutFld :: Maybe G.Name -> QualifiedTable -> Bool -> ObjFldInfo
mkInsMutFld mCustomName tn isUpsertable =
mkHsraObjFldInfo (Just desc) fldName (fromInpValL inputVals) $
G.toGT $ mkMutRespTy tn
where
inputVals = catMaybes [Just objectsArg , mkOnConflictInputVal tn isUpsertable]
desc = G.Description $
"insert data into the table: " <>> tn
defFldName = "insert_" <> qualObjectToName tn
fldName = fromMaybe defFldName mCustomName
objsArgDesc = "the rows to be inserted"
objectsArg =
InpValInfo (Just objsArgDesc) "objects" Nothing $ G.toGT $
G.toNT $ G.toLT $ G.toNT $ mkInsInpTy tn
mkConstraintTy :: QualifiedTable -> [ConstraintName] -> EnumTyInfo
mkConstraintTy tn cons = enumTyInfo
where
enumTyInfo = mkHsraEnumTyInfo (Just desc) (mkConstraintInpTy tn) $
EnumValuesSynthetic . mapFromL _eviVal $ map mkConstraintEnumVal cons
desc = G.Description $
"unique or primary key constraints on table " <>> tn
mkConstraintEnumVal (ConstraintName n) =
EnumValInfo (Just "unique or primary key constraint")
(G.EnumValue $ G.Name n) False
mkUpdColumnTy :: QualifiedTable -> [G.Name] -> EnumTyInfo
mkUpdColumnTy tn cols = enumTyInfo
where
enumTyInfo = mkHsraEnumTyInfo (Just desc) (mkUpdColumnInpTy tn) $
EnumValuesSynthetic . mapFromL _eviVal $ map mkColumnEnumVal cols
desc = G.Description $
"update columns of table " <>> tn
mkOnConflictTypes
:: QualifiedTable -> [ConstraintName] -> [G.Name] -> Bool -> [TypeInfo]
mkOnConflictTypes tn uniqueOrPrimaryCons cols =
bool [] tyInfos
where
tyInfos = [ TIEnum $ mkConstraintTy tn uniqueOrPrimaryCons
, TIEnum $ mkUpdColumnTy tn cols
, TIInpObj $ mkOnConflictInp tn
]
mkOnConflictInputVal :: QualifiedTable -> Bool -> Maybe InpValInfo
mkOnConflictInputVal qt =
bool Nothing (Just onConflictArg)
where
onConflictDesc = "on conflict condition"
onConflictArg = InpValInfo (Just onConflictDesc) "on_conflict"
Nothing $ G.toGT $ mkOnConflictInpTy qt
{-
insert_table_one(
object: table_insert_input!
on_conflict: table_on_conflict
): table
-}
mkInsertOneMutationField :: Maybe G.Name -> QualifiedTable -> Bool -> ObjFldInfo
mkInsertOneMutationField mCustomName qt isUpsertable =
mkHsraObjFldInfo (Just description) fieldName (fromInpValL inputVals) $
G.toGT $ mkTableTy qt
where
description = G.Description $ "insert a single row into the table: " <>> qt
fieldName = flip fromMaybe mCustomName $ "insert_" <> qualObjectToName qt <> "_one"
inputVals = catMaybes [Just objectArg, mkOnConflictInputVal qt isUpsertable]
objectArgDesc = "the row to be inserted"
objectArg = InpValInfo (Just objectArgDesc) "object" Nothing $ G.toGT $
G.toNT $ mkInsInpTy qt