{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE TemplateHaskellQuotes #-} module Hasura.GraphQL.Schema.Mutation ( insertIntoTable, insertOneIntoTable, deleteFromTable, deleteFromTableByPk, mkDefaultRelationshipParser, mutationSelectionSet, primaryKeysArguments, ) where import Control.Lens ((^.)) import Data.Has (getter) import Data.HashMap.Strict qualified as HashMap import Data.HashSet qualified as Set import Data.Text.Extended import Hasura.GraphQL.Schema.Backend import Hasura.GraphQL.Schema.BoolExp import Hasura.GraphQL.Schema.Common import Hasura.GraphQL.Schema.Parser ( FieldParser, InputFieldsParser, Kind (..), Parser, ) import Hasura.GraphQL.Schema.Parser qualified as P import Hasura.GraphQL.Schema.Select import Hasura.GraphQL.Schema.Table import Hasura.GraphQL.Schema.Typename import Hasura.Name qualified as Name import Hasura.Prelude import Hasura.RQL.IR.BoolExp import Hasura.RQL.IR.Delete qualified as IR import Hasura.RQL.IR.Insert qualified as IR import Hasura.RQL.IR.Returning qualified as IR import Hasura.RQL.IR.Root qualified as IR import Hasura.RQL.IR.Value qualified as IR import Hasura.RQL.Types.Backend import Hasura.RQL.Types.Column import Hasura.RQL.Types.Common import Hasura.RQL.Types.Metadata.Object import Hasura.RQL.Types.NamingCase (NamingCase) import Hasura.RQL.Types.Relationships.Local import Hasura.RQL.Types.SchemaCache hiding (askTableInfo) import Hasura.RQL.Types.Source import Hasura.RQL.Types.SourceCustomization import Hasura.SQL.AnyBackend qualified as AB import Hasura.Table.Cache import Language.GraphQL.Draft.Syntax qualified as G -- insert -- | Construct the parser for a field that can be used to add several rows to a DB table. -- -- This function is used to create the insert_tablename root field. -- The field accepts the following arguments: -- - objects: the list of objects to insert into the table (see 'tableFieldsInput') -- - parser for backend-specific fields, e.g. upsert fields on_conflict or if_matched insertIntoTable :: forall b r m n. (MonadBuildSchema b r m n) => (BackendTableSelectSchema b) => (TableInfo b -> SchemaT r m (InputFieldsParser n (BackendInsert b (IR.UnpreparedValue b)))) -> Scenario -> -- | qualified name of the table TableInfo b -> -- | field display name G.Name -> -- | field description, if any Maybe G.Description -> SchemaT r m (Maybe (FieldParser n (IR.AnnotatedInsert b (IR.RemoteRelationshipField IR.UnpreparedValue) (IR.UnpreparedValue b)))) insertIntoTable backendInsertAction scenario tableInfo fieldName description = runMaybeT $ do sourceInfo :: SourceInfo b <- asks getter roleName <- retrieve scRole let sourceName = _siName sourceInfo tableName = tableInfoName tableInfo viewInfo = _tciViewInfo $ _tiCoreInfo tableInfo permissions = getRolePermInfo roleName tableInfo tCase = _rscNamingConvention $ _siCustomization sourceInfo guard $ isMutable viIsInsertable viewInfo insertPerms <- hoistMaybe $ _permIns permissions -- If we're in a frontend scenario, we should not include backend_only inserts -- For more info see Note [Backend only permissions] guard $ not $ scenario == Frontend && ipiBackendOnly insertPerms lift do let updatePerms = _permUpd permissions -- objects [{ ... }] objectParser <- tableFieldsInput tableInfo backendInsertParser <- backendInsertAction tableInfo -- returning clause, affected rows, etc. selectionParser <- mutationSelectionSet tableInfo let argsParser = do backendInsert <- backendInsertParser objects <- mkObjectsArg objectParser pure $ mkInsertObject objects tableInfo backendInsert insertPerms updatePerms pure $ P.setFieldParserOrigin (MOSourceObjId sourceName (AB.mkAnyBackend $ SMOTable @b tableName)) $ P.subselection fieldName description argsParser selectionParser <&> \(insertObject, output) -> IR.AnnotatedInsert (G.unName fieldName) False insertObject (IR.MOutMultirowFields output) (Just tCase) where mkObjectsArg objectParser = P.field Name._objects (Just "the rows to be inserted") (P.list objectParser) -- | Variant of 'insertIntoTable' that inserts a single row. -- -- Instead of expecting a list of rows to insert in a 'objects' argument, this -- field instead expects a single 'object'. Its selection set is also slightly -- different: it only allows selecting columns from the row being inserted. insertOneIntoTable :: forall b r m n. (MonadBuildSchema b r m n) => (BackendTableSelectSchema b) => (TableInfo b -> SchemaT r m (InputFieldsParser n (BackendInsert b (IR.UnpreparedValue b)))) -> Scenario -> -- | table info TableInfo b -> -- | field display name G.Name -> -- | field description, if any Maybe G.Description -> SchemaT r m (Maybe (FieldParser n (IR.AnnotatedInsert b (IR.RemoteRelationshipField IR.UnpreparedValue) (IR.UnpreparedValue b)))) insertOneIntoTable backendInsertAction scenario tableInfo fieldName description = runMaybeT do sourceInfo :: SourceInfo b <- asks getter roleName <- retrieve scRole let sourceName = _siName sourceInfo tableName = tableInfoName tableInfo viewInfo = _tciViewInfo $ _tiCoreInfo tableInfo permissions = getRolePermInfo roleName tableInfo tCase = _rscNamingConvention $ _siCustomization sourceInfo guard $ isMutable viIsInsertable viewInfo insertPerms <- hoistMaybe $ _permIns permissions -- If we're in a frontend scenario, we should not include backend_only inserts -- For more info see Note [Backend only permissions] guard $ not $ scenario == Frontend && ipiBackendOnly insertPerms selectionParser <- MaybeT $ tableSelectionSet tableInfo lift do let updatePerms = _permUpd permissions objectParser <- tableFieldsInput tableInfo backendInsertParser <- backendInsertAction tableInfo let argsParser = do backendInsert <- backendInsertParser object <- mkObjectArg objectParser pure $ mkInsertObject [object] tableInfo backendInsert insertPerms updatePerms pure $ P.setFieldParserOrigin (MOSourceObjId sourceName (AB.mkAnyBackend $ SMOTable @b tableName)) $ P.subselection fieldName description argsParser selectionParser <&> \(insertObject, output) -> IR.AnnotatedInsert (G.unName fieldName) True insertObject (IR.MOutSinglerowObject output) (Just tCase) where mkObjectArg objectParser = P.field Name._object (Just "the row to be inserted") objectParser -- | Creates the parser for an input object for a row of the given table. -- -- This function creates an input object type named "tablename_insert_input" in -- the GraphQL shema, which has a field for each of the columns of that table -- that the user has insert permissions for. -- -- > { -- > insert_author ( -- > objects: [ -- > { # tableFieldsInput output -- > name: "John", -- > id:12 -- > } -- > ] ... -- > ) ... -- > } -- -- TODO: When there are no columns to insert, accessible to a role, -- this function may generate an empty input object. The GraphQL spec -- mandates that an input object type must define one or more input fields. -- In this case, when there are no columns that are accessible to a role, -- we should disallow generating the `insert_