graphql-engine/server/src-lib/Hasura/GraphQL/Schema/Mutation.hs
Auke Booij 8d0afb6f92
server: SQL that grows (#6003)
Add a backend type extension parameter to some RQL types, following the ideas of the paper "Trees that grow" (Najd & Jones 2016)

Co-authored-by: Antoine Leblanc <antoine@hasura.io>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-10-22 22:42:27 +02:00

533 lines
26 KiB
Haskell

{-# LANGUAGE ViewPatterns #-}
module Hasura.GraphQL.Schema.Mutation
( insertIntoTable
, insertOneIntoTable
, updateTable
, updateTableByPk
, deleteFromTable
, deleteFromTableByPk
) where
import Data.Has
import Hasura.Prelude
import qualified Data.HashMap.Strict as Map
import qualified Data.HashMap.Strict.InsOrd.Extended as OMap
import qualified Data.HashSet as Set
import qualified Data.Text as T
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Hasura.GraphQL.Parser as P
import qualified Hasura.RQL.DML.Delete.Types as RQL
import qualified Hasura.RQL.DML.Insert.Types as RQL
import qualified Hasura.RQL.DML.Returning.Types as RQL
import qualified Hasura.RQL.DML.Update as RQL
import qualified Hasura.RQL.DML.Update.Types as RQL
import qualified Hasura.SQL.DML as S
import Data.Text.Extended
import Hasura.GraphQL.Parser (FieldParser, InputFieldsParser, Kind (..),
Parser, UnpreparedValue (..), mkParameter)
import Hasura.GraphQL.Parser.Class
import Hasura.GraphQL.Schema.BoolExp
import Hasura.GraphQL.Schema.Common
import Hasura.GraphQL.Schema.Insert
import Hasura.GraphQL.Schema.Select
import Hasura.GraphQL.Schema.Table
import Hasura.RQL.Types
import Hasura.SQL.Types
-- insert
-- | Construct a root field, normally called insert_tablename, that can be used to add several rows to a DB table
insertIntoTable
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m, Has QueryContext r)
=> QualifiedTable -- ^ qualified name of the table
-> G.Name -- ^ field display name
-> Maybe G.Description -- ^ field description, if any
-> InsPermInfo 'Postgres -- ^ insert permissions of the table
-> Maybe (SelPermInfo 'Postgres) -- ^ select permissions of the table (if any)
-> Maybe (UpdPermInfo 'Postgres) -- ^ update permissions of the table (if any)
-> m (FieldParser n (AnnInsert 'Postgres UnpreparedValue))
insertIntoTable table fieldName description insertPerms selectPerms updatePerms = do
columns <- tableColumns table
selectionParser <- mutationSelectionSet table selectPerms
objectsParser <- P.list <$> tableFieldsInput table insertPerms
conflictParser <- fmap join $ sequenceA $ conflictObject table selectPerms <$> updatePerms
let objectsName = $$(G.litName "objects")
objectsDesc = "the rows to be inserted"
argsParser = do
conflictClause <- mkConflictClause conflictParser
objects <- P.field objectsName (Just objectsDesc) objectsParser
pure (conflictClause, objects)
pure $ P.subselection fieldName description argsParser selectionParser
<&> \((conflictClause, objects), output) -> AnnInsert (G.unName fieldName) False
( mkInsertObject objects table columns conflictClause insertPerms updatePerms
, RQL.MOutMultirowFields output
)
mkConflictClause :: MonadParse n => Maybe (Parser 'Input n a) -> InputFieldsParser n (Maybe a)
mkConflictClause conflictParser
= maybe
(pure Nothing) -- Empty InputFieldsParser (no arguments allowed)
(P.fieldOptional conflictName (Just conflictDesc))
conflictParser
where
conflictName = $$(G.litName "on_conflict")
conflictDesc = "on conflict condition"
-- | Variant of 'insertIntoTable' that inserts a single row
insertOneIntoTable
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m, Has QueryContext r)
=> QualifiedTable -- ^ qualified name of the table
-> G.Name -- ^ field display name
-> Maybe G.Description -- ^ field description, if any
-> InsPermInfo 'Postgres -- ^ insert permissions of the table
-> SelPermInfo 'Postgres -- ^ select permissions of the table
-> Maybe (UpdPermInfo 'Postgres) -- ^ update permissions of the table (if any)
-> m (FieldParser n (AnnInsert 'Postgres UnpreparedValue))
insertOneIntoTable table fieldName description insertPerms selectPerms updatePerms = do
columns <- tableColumns table
selectionParser <- tableSelectionSet table selectPerms
objectParser <- tableFieldsInput table insertPerms
conflictParser <- fmap join $ sequenceA $ conflictObject table (Just selectPerms) <$> updatePerms
let objectName = $$(G.litName "object")
objectDesc = "the row to be inserted"
argsParser = do
conflictClause <- mkConflictClause conflictParser
object <- P.field objectName (Just objectDesc) objectParser
pure (conflictClause, object)
pure $ P.subselection fieldName description argsParser selectionParser
<&> \((conflictClause, object), output) -> AnnInsert (G.unName fieldName) True
( mkInsertObject [object] table columns conflictClause insertPerms updatePerms
, RQL.MOutSinglerowObject output
)
-- | We specify the data of an individual row to insert through this input parser.
tableFieldsInput
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m)
=> QualifiedTable -- ^ qualified name of the table
-> InsPermInfo 'Postgres -- ^ insert permissions of the table
-> m (Parser 'Input n (AnnInsObj 'Postgres UnpreparedValue))
tableFieldsInput table insertPerms = memoizeOn 'tableFieldsInput table do
tableName <- qualifiedObjectToName table
allFields <- _tciFieldInfoMap . _tiCoreInfo <$> askTableInfo table
objectFields <- catMaybes <$> for (Map.elems allFields) \case
FIComputedField _ -> pure Nothing
FIRemoteRelationship _ -> pure Nothing
FIColumn columnInfo ->
whenMaybe (Set.member (pgiColumn columnInfo) (ipiCols insertPerms)) do
let columnName = pgiName columnInfo
columnDesc = pgiDescription columnInfo
fieldParser <- P.column (pgiType columnInfo) (G.Nullability $ pgiIsNullable columnInfo)
pure $ P.fieldOptional columnName columnDesc fieldParser `mapField`
\(mkParameter -> value) -> AnnInsObj [(pgiColumn columnInfo, value)] [] []
FIRelationship relationshipInfo -> runMaybeT $ do
let otherTable = riRTable relationshipInfo
relName = riName relationshipInfo
permissions <- MaybeT $ tablePermissions otherTable
relFieldName <- lift $ textToName $ relNameToTxt relName
insPerms <- MaybeT $ pure $ _permIns permissions
let selPerms = _permSel permissions
updPerms = _permUpd permissions
lift $ case riType relationshipInfo of
ObjRel -> do
parser <- objectRelationshipInput otherTable insPerms selPerms updPerms
pure $ P.fieldOptional relFieldName Nothing parser `mapField`
\objRelIns -> AnnInsObj [] [RelIns objRelIns relationshipInfo] []
ArrRel -> do
parser <- P.nullable <$> arrayRelationshipInput otherTable insPerms selPerms updPerms
pure $ P.fieldOptional relFieldName Nothing parser <&> \arrRelIns -> do
rel <- join arrRelIns
Just $ AnnInsObj [] [] [RelIns rel relationshipInfo | not $ null $ _aiInsObj rel]
let objectName = tableName <> $$(G.litName "_insert_input")
objectDesc = G.Description $ "input type for inserting data into table " <>> table
pure $ P.object objectName (Just objectDesc) $ catMaybes <$> sequenceA objectFields
<&> mconcat
-- | Used by 'tableFieldsInput' for object data that is nested through object relationships
objectRelationshipInput
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m)
=> QualifiedTable
-> InsPermInfo 'Postgres
-> Maybe (SelPermInfo 'Postgres)
-> Maybe (UpdPermInfo 'Postgres)
-> m (Parser 'Input n (SingleObjIns 'Postgres UnpreparedValue))
objectRelationshipInput table insertPerms selectPerms updatePerms =
memoizeOn 'objectRelationshipInput table do
tableName <- qualifiedObjectToName table
columns <- tableColumns table
objectParser <- tableFieldsInput table insertPerms
conflictParser <- fmap join $ sequenceA $ conflictObject table selectPerms <$> updatePerms
let objectName = $$(G.litName "data")
inputName = tableName <> $$(G.litName "_obj_rel_insert_input")
inputDesc = G.Description $ "input type for inserting object relation for remote table " <>> table
inputParser = do
conflictClause <- mkConflictClause conflictParser
object <- P.field objectName Nothing objectParser
pure $ mkInsertObject object table columns conflictClause insertPerms updatePerms
pure $ P.object inputName (Just inputDesc) inputParser
-- | Used by 'tableFieldsInput' for object data that is nested through object relationships
arrayRelationshipInput
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m)
=> QualifiedTable
-> InsPermInfo 'Postgres
-> Maybe (SelPermInfo 'Postgres)
-> Maybe (UpdPermInfo 'Postgres)
-> m (Parser 'Input n (MultiObjIns 'Postgres UnpreparedValue))
arrayRelationshipInput table insertPerms selectPerms updatePerms =
memoizeOn 'arrayRelationshipInput table do
tableName <- qualifiedObjectToName table
columns <- tableColumns table
objectParser <- tableFieldsInput table insertPerms
conflictParser <- fmap join $ sequenceA $ conflictObject table selectPerms <$> updatePerms
let objectsName = $$(G.litName "data")
inputName = tableName <> $$(G.litName "_arr_rel_insert_input")
inputDesc = G.Description $ "input type for inserting array relation for remote table " <>> table
inputParser = do
conflictClause <- mkConflictClause conflictParser
objects <- P.field objectsName Nothing $ P.list objectParser
pure $ mkInsertObject objects table columns conflictClause insertPerms updatePerms
pure $ P.object inputName (Just inputDesc) inputParser
mkInsertObject
:: a
-> QualifiedTable
-> [ColumnInfo 'Postgres]
-> Maybe (RQL.ConflictClauseP1 'Postgres UnpreparedValue)
-> InsPermInfo 'Postgres
-> Maybe (UpdPermInfo 'Postgres)
-> AnnIns 'Postgres a UnpreparedValue
mkInsertObject objects table columns conflictClause insertPerms updatePerms =
AnnIns { _aiInsObj = objects
, _aiTableName = table
, _aiConflictClause = conflictClause
, _aiCheckCond = (insertCheck, updateCheck)
, _aiTableCols = columns
, _aiDefVals = defaultValues
}
where insertCheck = fmapAnnBoolExp partialSQLExpToUnpreparedValue $ ipiCheck insertPerms
updateCheck = fmapAnnBoolExp partialSQLExpToUnpreparedValue <$> (upiCheck =<< updatePerms)
defaultValues = Map.union (partialSQLExpToUnpreparedValue <$> ipiSet insertPerms)
$ fmap UVLiteral $ S.mkColDefValMap $ map pgiColumn columns
-- | Specifies the "ON CONFLICT" SQL clause
conflictObject
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m)
=> QualifiedTable
-> Maybe (SelPermInfo 'Postgres)
-> UpdPermInfo 'Postgres
-> m (Maybe (Parser 'Input n (RQL.ConflictClauseP1 'Postgres UnpreparedValue)))
conflictObject table selectPerms updatePerms = runMaybeT $ do
tableName <- lift $ qualifiedObjectToName table
columnsEnum <- MaybeT $ tableUpdateColumnsEnum table updatePerms
constraints <- MaybeT $ tciUniqueOrPrimaryKeyConstraints . _tiCoreInfo <$> askTableInfo table
constraintParser <- lift $ conflictConstraint constraints table
whereExpParser <- lift $ boolExp table selectPerms
let objectName = tableName <> $$(G.litName "_on_conflict")
objectDesc = G.Description $ "on conflict condition type for table " <>> table
constraintName = $$(G.litName "constraint")
columnsName = $$(G.litName "update_columns")
whereExpName = $$(G.litName "where")
fieldsParser = do
constraint <- RQL.CTConstraint <$> P.field constraintName Nothing constraintParser
columns <- P.field columnsName Nothing $ P.list columnsEnum
whereExp <- P.fieldOptional whereExpName Nothing whereExpParser
pure $ case columns of
[] -> RQL.CP1DoNothing $ Just constraint
_ -> RQL.CP1Update constraint columns preSetColumns $
BoolAnd $ catMaybes [whereExp, Just $ fmapAnnBoolExp partialSQLExpToUnpreparedValue $ upiFilter updatePerms]
pure $ P.object objectName (Just objectDesc) fieldsParser
where preSetColumns = partialSQLExpToUnpreparedValue <$> upiSet updatePerms
conflictConstraint
:: forall m n r. (MonadSchema n m, MonadTableInfo r m)
=> NonEmpty Constraint
-> QualifiedTable
-> m (Parser 'Both n ConstraintName)
conflictConstraint constraints table = memoizeOn 'conflictConstraint table $ do
tableName <- qualifiedObjectToName table
constraintEnumValues <- for constraints \constraint -> do
name <- textToName $ getConstraintTxt $ _cName constraint
pure ( P.mkDefinition name (Just "unique or primary key constraint") P.EnumValueInfo
, _cName constraint
)
let enumName = tableName <> $$(G.litName "_constraint")
enumDesc = G.Description $ "unique or primary key constraints on table " <>> table
pure $ P.enum enumName (Just enumDesc) constraintEnumValues
-- update
-- | Construct a root field, normally called update_tablename, that can be used
-- to update rows in a DB table specified by filters. Only returns a parser if
-- there are columns the user is allowed to update; otherwise returns Nothing.
updateTable
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m, Has QueryContext r)
=> QualifiedTable -- ^ qualified name of the table
-> G.Name -- ^ field display name
-> Maybe G.Description -- ^ field description, if any
-> UpdPermInfo 'Postgres -- ^ update permissions of the table
-> Maybe (SelPermInfo 'Postgres) -- ^ select permissions of the table (if any)
-> m (Maybe (FieldParser n (RQL.AnnUpdG 'Postgres UnpreparedValue)))
updateTable table fieldName description updatePerms selectPerms = runMaybeT $ do
let whereName = $$(G.litName "where")
whereDesc = "filter the rows which have to be updated"
opArgs <- MaybeT $ updateOperators table updatePerms
columns <- lift $ tableColumns table
whereArg <- lift $ P.field whereName (Just whereDesc) <$> boolExp table selectPerms
selection <- lift $ mutationSelectionSet table selectPerms
let argsParser = liftA2 (,) opArgs whereArg
pure $ P.subselection fieldName description argsParser selection
<&> mkUpdateObject table columns updatePerms . fmap RQL.MOutMultirowFields
-- | Construct a root field, normally called update_tablename, that can be used
-- to update a single in a DB table, specified by primary key. Only returns a
-- parser if there are columns the user is allowed to update and if the user has
-- select permissions on all primary keys; otherwise returns Nothing.
updateTableByPk
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m, Has QueryContext r)
=> QualifiedTable -- ^ qualified name of the table
-> G.Name -- ^ field display name
-> Maybe G.Description -- ^ field description, if any
-> UpdPermInfo 'Postgres -- ^ update permissions of the table
-> SelPermInfo 'Postgres -- ^ select permissions of the table
-> m (Maybe (FieldParser n (RQL.AnnUpdG 'Postgres UnpreparedValue)))
updateTableByPk table fieldName description updatePerms selectPerms = runMaybeT $ do
tableName <- qualifiedObjectToName table
columns <- lift $ tableSelectColumns table selectPerms
pkArgs <- MaybeT $ primaryKeysArguments table selectPerms
opArgs <- MaybeT $ updateOperators table updatePerms
selection <- lift $ tableSelectionSet table selectPerms
let pkFieldName = $$(G.litName "pk_columns")
pkObjectName = tableName <> $$(G.litName "_pk_columns_input")
pkObjectDesc = G.Description $ "primary key columns input for table: " <> G.unName tableName
argsParser = do
operators <- opArgs
primaryKeys <- P.field pkFieldName Nothing $ P.object pkObjectName (Just pkObjectDesc) pkArgs
pure (operators, primaryKeys)
pure $ P.subselection fieldName description argsParser selection
<&> mkUpdateObject table columns updatePerms . fmap RQL.MOutSinglerowObject
mkUpdateObject
:: QualifiedTable
-> [ColumnInfo 'Postgres]
-> UpdPermInfo 'Postgres
-> ( ( [(PGCol, RQL.UpdOpExpG UnpreparedValue)]
, AnnBoolExp 'Postgres UnpreparedValue
)
, RQL.MutationOutputG 'Postgres UnpreparedValue
)
-> RQL.AnnUpdG 'Postgres UnpreparedValue
mkUpdateObject table columns updatePerms ((opExps, whereExp), mutationOutput) =
RQL.AnnUpd { RQL.uqp1Table = table
, RQL.uqp1OpExps = opExps
, RQL.uqp1Where = (permissionFilter, whereExp)
, RQL.uqp1Check = checkExp
, RQL.uqp1Output = mutationOutput
, RQL.uqp1AllCols = columns
}
where
permissionFilter = fmapAnnBoolExp partialSQLExpToUnpreparedValue $ upiFilter updatePerms
checkExp = maybe annBoolExpTrue (fmapAnnBoolExp partialSQLExpToUnpreparedValue) $ upiCheck updatePerms
-- | Various update operators
updateOperators
:: forall m n r. (MonadSchema n m, MonadTableInfo r m)
=> QualifiedTable -- ^ qualified name of the table
-> UpdPermInfo 'Postgres -- ^ update permissions of the table
-> m (Maybe (InputFieldsParser n [(PGCol, RQL.UpdOpExpG UnpreparedValue)]))
updateOperators table updatePermissions = do
tableName <- qualifiedObjectToName table
columns <- tableUpdateColumns table updatePermissions
let numericCols = onlyNumCols columns
jsonCols = onlyJSONBCols columns
parsers <- catMaybes <$> sequenceA
[ updateOperator tableName $$(G.litName "_set")
columnParser RQL.UpdSet columns
"sets the columns of the filtered rows to the given values"
(G.Description $ "input type for updating data in table " <>> table)
, updateOperator tableName $$(G.litName "_inc")
columnParser RQL.UpdInc numericCols
"increments the numeric columns with given value of the filtered values"
(G.Description $"input type for incrementing numeric columns in table " <>> table)
, let desc = "prepend existing jsonb value of filtered columns with new jsonb value"
in updateOperator tableName $$(G.litName "_prepend")
columnParser RQL.UpdPrepend jsonCols desc desc
, let desc = "append existing jsonb value of filtered columns with new jsonb value"
in updateOperator tableName $$(G.litName "_append")
columnParser RQL.UpdAppend jsonCols desc desc
, let desc = "delete key/value pair or string element. key/value pairs are matched based on their key value"
in updateOperator tableName $$(G.litName "_delete_key")
nullableTextParser RQL.UpdDeleteKey jsonCols desc desc
, let desc = "delete the array element with specified index (negative integers count from the end). "
<> "throws an error if top level container is not an array"
in updateOperator tableName $$(G.litName "_delete_elem")
nonNullableIntParser RQL.UpdDeleteElem jsonCols desc desc
, let desc = "delete the field or element with specified path (for JSON arrays, negative integers count from the end)"
in updateOperator tableName $$(G.litName "_delete_at_path")
(fmap P.list . nonNullableTextParser) RQL.UpdDeleteAtPath jsonCols desc desc
]
whenMaybe (not $ null parsers) do
let allowedOperators = fst <$> parsers
pure $ fmap catMaybes (sequenceA $ snd <$> parsers)
`P.bindFields` \opExps -> do
-- there needs to be at least one operator in the update, even if it is empty
let presetColumns = Map.toList $ RQL.UpdSet . partialSQLExpToUnpreparedValue <$> upiSet updatePermissions
when (null opExps && null presetColumns) $ parseError $
"at least any one of " <> commaSeparated allowedOperators <> " is expected"
-- no column should appear twice
let flattenedExps = concat opExps
erroneousExps = OMap.filter ((>1) . length) $ OMap.groupTuples flattenedExps
unless (OMap.null erroneousExps) $ parseError $
"column found in multiple operators; " <>
T.intercalate ". " [ dquote column <> " in " <> commaSeparated (RQL.updateOperatorText <$> ops)
| (column, ops) <- OMap.toList erroneousExps
]
pure $ presetColumns <> flattenedExps
where
columnParser columnInfo = fmap P.mkParameter <$> P.column (pgiType columnInfo) (G.Nullability $ pgiIsNullable columnInfo)
nonNullableTextParser _ = fmap P.mkParameter <$> P.column (PGColumnScalar PGText) (G.Nullability False)
nullableTextParser _ = fmap P.mkParameter <$> P.column (PGColumnScalar PGText) (G.Nullability True)
nonNullableIntParser _ = fmap P.mkParameter <$> P.column (PGColumnScalar PGInteger) (G.Nullability False)
updateOperator
:: G.Name
-> G.Name
-> (ColumnInfo 'Postgres -> m (Parser 'Both n a))
-> (a -> RQL.UpdOpExpG UnpreparedValue)
-> [ColumnInfo 'Postgres]
-> G.Description
-> G.Description
-> m (Maybe (Text, InputFieldsParser n (Maybe [(PGCol, RQL.UpdOpExpG UnpreparedValue)])))
updateOperator tableName opName mkParser updOpExp columns opDesc objDesc =
whenMaybe (not $ null columns) do
fields <- for columns \columnInfo -> do
let fieldName = pgiName columnInfo
fieldDesc = pgiDescription columnInfo
fieldParser <- mkParser columnInfo
pure $ P.fieldOptional fieldName fieldDesc fieldParser
`mapField` \value -> (pgiColumn columnInfo, updOpExp value)
let objName = tableName <> opName <> $$(G.litName "_input")
pure $ (G.unName opName,)
$ P.fieldOptional opName (Just opDesc)
$ P.object objName (Just objDesc)
$ catMaybes <$> sequenceA fields
-- delete
-- | Construct a root field, normally called delete_tablename, that can be used
-- to delete several rows from a DB table
deleteFromTable
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m, Has QueryContext r)
=> QualifiedTable -- ^ qualified name of the table
-> G.Name -- ^ field display name
-> Maybe G.Description -- ^ field description, if any
-> DelPermInfo 'Postgres -- ^ delete permissions of the table
-> Maybe (SelPermInfo 'Postgres) -- ^ select permissions of the table (if any)
-> m (FieldParser n (RQL.AnnDelG 'Postgres UnpreparedValue))
deleteFromTable table fieldName description deletePerms selectPerms = do
let whereName = $$(G.litName "where")
whereDesc = "filter the rows which have to be deleted"
whereArg <- P.field whereName (Just whereDesc) <$> boolExp table selectPerms
selection <- mutationSelectionSet table selectPerms
columns <- tableColumns table
pure $ P.subselection fieldName description whereArg selection
<&> mkDeleteObject table columns deletePerms . fmap RQL.MOutMultirowFields
-- | Construct a root field, normally called delete_tablename, that can be used
-- to delete an individual rows from a DB table, specified by primary key
deleteFromTableByPk
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m, Has QueryContext r)
=> QualifiedTable -- ^ qualified name of the table
-> G.Name -- ^ field display name
-> Maybe G.Description -- ^ field description, if any
-> DelPermInfo 'Postgres -- ^ delete permissions of the table
-> SelPermInfo 'Postgres -- ^ select permissions of the table
-> m (Maybe (FieldParser n (RQL.AnnDelG 'Postgres UnpreparedValue)))
deleteFromTableByPk table fieldName description deletePerms selectPerms = runMaybeT $ do
columns <- lift $ tableSelectColumns table selectPerms
pkArgs <- MaybeT $ primaryKeysArguments table selectPerms
selection <- lift $ tableSelectionSet table selectPerms
pure $ P.subselection fieldName description pkArgs selection
<&> mkDeleteObject table columns deletePerms . fmap RQL.MOutSinglerowObject
mkDeleteObject
:: QualifiedTable
-> [ColumnInfo 'Postgres]
-> DelPermInfo 'Postgres
-> (AnnBoolExp 'Postgres UnpreparedValue, RQL.MutationOutputG 'Postgres UnpreparedValue)
-> RQL.AnnDelG 'Postgres UnpreparedValue
mkDeleteObject table columns deletePerms (whereExp, mutationOutput) =
RQL.AnnDel { RQL.dqp1Table = table
, RQL.dqp1Where = (permissionFilter, whereExp)
, RQL.dqp1Output = mutationOutput
, RQL.dqp1AllCols = columns
}
where
permissionFilter = fmapAnnBoolExp partialSQLExpToUnpreparedValue $ dpiFilter deletePerms
-- common
-- | All mutations allow returning results, such as what the updated database
-- rows look like. This parser allows a query to specify what data to fetch.
mutationSelectionSet
:: forall m n r. (MonadSchema n m, MonadTableInfo r m, MonadRole r m, Has QueryContext r)
=> QualifiedTable
-> Maybe (SelPermInfo 'Postgres)
-> m (Parser 'Output n (RQL.MutFldsG 'Postgres UnpreparedValue))
mutationSelectionSet table selectPerms =
memoizeOn 'mutationSelectionSet table do
tableName <- qualifiedObjectToName table
returning <- runMaybeT do
permissions <- MaybeT $ pure selectPerms
tableSet <- lift $ tableSelectionList table permissions
let returningName = $$(G.litName "returning")
returningDesc = "data from the rows affected by the mutation"
pure $ RQL.MRet <$> P.subselection_ returningName (Just returningDesc) tableSet
let affectedRowsName = $$(G.litName "affected_rows")
affectedRowsDesc = "number of rows affected by the mutation"
selectionName = tableName <> $$(G.litName "_mutation_response")
selectionDesc = G.Description $ "response of any mutation on the table " <>> table
selectionFields = catMaybes
[ Just $ RQL.MCount <$
P.selection_ affectedRowsName (Just affectedRowsDesc) P.int
, returning
]
pure $ P.selectionSet selectionName (Just selectionDesc) selectionFields
<&> parsedSelectionsToFields RQL.MExp
-- | How to specify a database row by primary key.
primaryKeysArguments
:: forall m n r. (MonadSchema n m, MonadTableInfo r m)
=> QualifiedTable
-> SelPermInfo 'Postgres
-> m (Maybe (InputFieldsParser n (AnnBoolExp 'Postgres UnpreparedValue)))
primaryKeysArguments table selectPerms = runMaybeT $ do
primaryKeys <- MaybeT $ _tciPrimaryKey . _tiCoreInfo <$> askTableInfo table
let columns = _pkColumns primaryKeys
guard $ all (\c -> pgiColumn c `Set.member` spiCols selectPerms) columns
lift $ fmap (BoolAnd . toList) . sequenceA <$> for columns \columnInfo -> do
field <- P.column (pgiType columnInfo) (G.Nullability False)
pure $ BoolFld . AVCol columnInfo . pure . AEQ True . mkParameter <$>
P.field (pgiName columnInfo) (pgiDescription columnInfo) field