graphql-engine/server/src-lib/Hasura/RQL/DML/Delete.hs
Rakesh Emmadi 5bafdce9a3 fix delete mutation returning incorrect data (fix #1794) (fix #1763) (#1827)
From `alpha-40` we've been using a `WHERE` clause to fetch required rows and generate mutation response. This has a few limitations like the requirement of a primary key/unique constraint. This also returns inconsistent data on `delete` mutation as mentioned in #1794. 
Now, we're using `VALUES (..)` (refer [here](https://www.postgresql.org/docs/current/sql-values.html)) expression to form virtual table rows in `SQL` to generate mutation response.

Internal changes:-
- Not to use primary key/unique constraint columns:-
  - Revert back to `ConstraintName` from `TableConstraint` in `TableInfo` type
  - Remove `tcCols` field in `TableConstraint` type
  - Modify `table_info.sql` and `fetchTableMeta` function `SQL`
- A test case to perform `delete` mutation and returning relational objects.
2019-03-22 12:38:42 +05:30

116 lines
3.5 KiB
Haskell

module Hasura.RQL.DML.Delete
( validateDeleteQWith
, validateDeleteQ
, DeleteQueryP1(..)
, deleteQueryToTx
, getDeleteDeps
, runDelete
) where
import Data.Aeson
import Instances.TH.Lift ()
import qualified Data.Sequence as DS
import Hasura.EncJSON
import Hasura.Prelude
import Hasura.RQL.DML.Internal
import Hasura.RQL.DML.Mutation
import Hasura.RQL.DML.Returning
import Hasura.RQL.GBoolExp
import Hasura.RQL.Types
import Hasura.SQL.Types
import qualified Database.PG.Query as Q
import qualified Hasura.SQL.DML as S
data DeleteQueryP1
= DeleteQueryP1
{ dqp1Table :: !QualifiedTable
, dqp1Where :: !(AnnBoolExpSQL, AnnBoolExpSQL)
, dqp1MutFlds :: !MutFlds
, dqp1AllCols :: ![PGColInfo]
} deriving (Show, Eq)
mkDeleteCTE
:: DeleteQueryP1 -> S.CTE
mkDeleteCTE (DeleteQueryP1 tn (fltr, wc) _ _) =
S.CTEDelete delete
where
delete = S.SQLDelete tn Nothing tableFltr $ Just S.returningStar
tableFltr = Just $ S.WhereFrag $
toSQLBoolExp (S.QualTable tn) $ andAnnBoolExps fltr wc
getDeleteDeps
:: DeleteQueryP1 -> [SchemaDependency]
getDeleteDeps (DeleteQueryP1 tn (_, wc) mutFlds allCols) =
mkParentDep tn : allColDeps <> whereDeps <> retDeps
where
whereDeps = getBoolExpDeps tn wc
allColDeps = map (mkColDep "on_type" tn . pgiName) allCols
retDeps = map (mkColDep "untyped" tn . fst) $
pgColsFromMutFlds mutFlds
validateDeleteQWith
:: (UserInfoM m, QErrM m, CacheRM m)
=> (PGColType -> Value -> m S.SQLExp)
-> DeleteQuery
-> m DeleteQueryP1
validateDeleteQWith prepValBuilder (DeleteQuery tableName rqlBE mRetCols) = do
tableInfo <- askTabInfo tableName
-- If table is view then check if it deletable
mutableView tableName viIsDeletable
(tiViewInfo tableInfo) "deletable"
-- Check if the role has delete permissions
delPerm <- askDelPermInfo tableInfo
-- Check if all dependent headers are present
validateHeaders $ dpiRequiredHeaders delPerm
-- Check if select is allowed
selPerm <- modifyErr (<> selNecessaryMsg) $
askSelPermInfo tableInfo
let fieldInfoMap = tiFieldInfoMap tableInfo
allCols = getCols fieldInfoMap
-- convert the returning cols into sql returing exp
mAnnRetCols <- forM mRetCols $ \retCols ->
withPathK "returning" $ checkRetCols fieldInfoMap selPerm retCols
-- convert the where clause
annSQLBoolExp <- withPathK "where" $
convBoolExp' fieldInfoMap selPerm rqlBE prepValBuilder
return $ DeleteQueryP1 tableName
(dpiFilter delPerm, annSQLBoolExp)
(mkDefaultMutFlds mAnnRetCols) allCols
where
selNecessaryMsg =
"; \"delete\" is only allowed if the role "
<> "has \"select\" permission as \"where\" can't be used "
<> "without \"select\" permission on the table"
validateDeleteQ
:: (QErrM m, UserInfoM m, CacheRM m, HasSQLGenCtx m)
=> DeleteQuery -> m (DeleteQueryP1, DS.Seq Q.PrepArg)
validateDeleteQ =
liftDMLP1 . validateDeleteQWith binRHSBuilder
deleteQueryToTx :: Bool -> (DeleteQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr EncJSON
deleteQueryToTx strfyNum (u, p) =
runMutation $ Mutation (dqp1Table u) (deleteCTE, p)
(dqp1MutFlds u) (dqp1AllCols u) strfyNum
where
deleteCTE = mkDeleteCTE u
runDelete
:: (QErrM m, UserInfoM m, CacheRM m, MonadTx m, HasSQLGenCtx m)
=> DeleteQuery -> m EncJSON
runDelete q = do
strfyNum <- stringifyNum <$> askSQLGenCtx
validateDeleteQ q >>= liftTx . deleteQueryToTx strfyNum