server: avoid raising mutation check constraint violation in pg procedure (#6123)

https://github.com/hasura/graphql-engine/pull/6123
This commit is contained in:
Rakesh Emmadi 2020-11-06 18:52:22 +05:30 committed by GitHub
parent 01c4cd2ff3
commit ca47c92f50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 226 additions and 166 deletions

View File

@ -279,7 +279,7 @@ Now, we can create a role ``user`` and add an insert validation rule as follows:
:alt: validation using permission: title cannot be empty
.. tab:: CLI
You can add roles and permissions in the ``tables.yaml`` file inside the ``metadata`` directory:
.. code-block:: yaml
@ -345,7 +345,7 @@ If we try to insert an article with ``title = ""``, we will get a ``permission-e
{
"errors": [
{
"message": "Check constraint violation. insert check constraint failed",
"message": "check constraint of an insert/update permission has failed",
"extensions": {
"path": "$.selectionSet.insert_article.args.objects",
"code": "permission-error"
@ -449,7 +449,7 @@ will receive a ``permission-error`` :
{
"errors": [
{
"message": "Check constraint violation. insert check constraint failed",
"message": "check constraint of an insert/update permission has failed",
"extensions": {
"path": "$.selectionSet.insert_article.args.objects",
"code": "permission-error"
@ -545,7 +545,7 @@ we get the following error message:
InsertAuthor(author: { name: "Thanos" }) {
id
}
}
}
:response:
{
"errors": [

View File

@ -35,16 +35,16 @@ import Hasura.Backends.Postgres.Translate.Select
import Hasura.Backends.Postgres.Translate.Update
import Hasura.EncJSON
import Hasura.RQL.DML.Internal
import Hasura.RQL.Instances ()
import Hasura.RQL.IR.Delete
import Hasura.RQL.IR.Insert
import Hasura.RQL.IR.Returning
import Hasura.RQL.IR.Select
import Hasura.RQL.IR.Update
import Hasura.RQL.Instances ()
import Hasura.RQL.Types
import Hasura.SQL.Types
import Hasura.Server.Version (HasVersion)
import Hasura.Session
import Hasura.SQL.Types
type MutationRemoteJoinCtx = (HTTP.Manager, [N.Header], UserInfo)
@ -52,7 +52,7 @@ type MutationRemoteJoinCtx = (HTTP.Manager, [N.Header], UserInfo)
data Mutation (b :: Backend)
= Mutation
{ _mTable :: !QualifiedTable
, _mQuery :: !(S.CTE, DS.Seq Q.PrepArg)
, _mQuery :: !(MutationCTE, DS.Seq Q.PrepArg)
, _mOutput :: !(MutationOutput b)
, _mCols :: ![ColumnInfo b]
, _mRemoteJoins :: !(Maybe (RemoteJoins b, MutationRemoteJoinCtx))
@ -62,7 +62,7 @@ data Mutation (b :: Backend)
mkMutation
:: Maybe MutationRemoteJoinCtx
-> QualifiedTable
-> (S.CTE, DS.Seq Q.PrepArg)
-> (MutationCTE, DS.Seq Q.PrepArg)
-> MutationOutput 'Postgres
-> [ColumnInfo 'Postgres]
-> Bool
@ -97,10 +97,7 @@ mutateAndReturn
-> Mutation 'Postgres
-> m EncJSON
mutateAndReturn env (Mutation qt (cte, p) mutationOutput allCols remoteJoins strfyNum) =
executeMutationOutputQuery env sqlQuery (toList p) remoteJoins
where
sqlQuery = Q.fromBuilder $ toSQL $
mkMutationOutputExp qt allCols Nothing cte mutationOutput strfyNum
executeMutationOutputQuery env qt allCols Nothing cte mutationOutput strfyNum (toList p) remoteJoins
execUpdateQuery
@ -116,7 +113,7 @@ execUpdateQuery
-> (AnnUpd 'Postgres, DS.Seq Q.PrepArg)
-> m EncJSON
execUpdateQuery env strfyNum remoteJoinCtx (u, p) =
runMutation env $ mkMutation remoteJoinCtx (uqp1Table u) (updateCTE, p)
runMutation env $ mkMutation remoteJoinCtx (uqp1Table u) (MCCheckConstraint updateCTE, p)
(uqp1Output u) (uqp1AllCols u) strfyNum
where
updateCTE = mkUpdateCTE u
@ -134,10 +131,10 @@ execDeleteQuery
-> (AnnDel 'Postgres, DS.Seq Q.PrepArg)
-> m EncJSON
execDeleteQuery env strfyNum remoteJoinCtx (u, p) =
runMutation env $ mkMutation remoteJoinCtx (dqp1Table u) (deleteCTE, p)
runMutation env $ mkMutation remoteJoinCtx (dqp1Table u) (MCDelete delete, p)
(dqp1Output u) (dqp1AllCols u) strfyNum
where
deleteCTE = mkDeleteCTE u
delete = mkDelete u
execInsertQuery
:: ( HasVersion
@ -152,7 +149,7 @@ execInsertQuery
-> m EncJSON
execInsertQuery env strfyNum remoteJoinCtx (u, p) =
runMutation env
$ mkMutation remoteJoinCtx (iqp1Table u) (insertCTE, p)
$ mkMutation remoteJoinCtx (iqp1Table u) (MCCheckConstraint insertCTE, p)
(iqp1Output u) (iqp1AllCols u) strfyNum
where
insertCTE = mkInsertCTE u
@ -186,41 +183,67 @@ mutateAndSel
mutateAndSel env (Mutation qt q mutationOutput allCols remoteJoins strfyNum) = do
-- Perform mutation and fetch unique columns
MutateResp _ columnVals <- liftTx $ mutateAndFetchCols qt allCols q strfyNum
selCTE <- mkSelCTEFromColVals qt allCols columnVals
let selWith = mkMutationOutputExp qt allCols Nothing selCTE mutationOutput strfyNum
select <- mkSelectExpFromColumnValues qt allCols columnVals
-- Perform select query and fetch returning fields
executeMutationOutputQuery env (Q.fromBuilder $ toSQL selWith) [] remoteJoins
executeMutationOutputQuery env qt allCols Nothing
(MCSelectValues select) mutationOutput strfyNum [] remoteJoins
withCheckPermission :: (MonadError QErr m) => m (a, Bool) -> m a
withCheckPermission sqlTx = do
(rawResponse, checkConstraint) <- sqlTx
unless checkConstraint $ throw400 PermissionError $
"check constraint of an insert/update permission has failed"
pure rawResponse
executeMutationOutputQuery
::
:: forall m.
( HasVersion
, MonadTx m
, MonadIO m
, Tracing.MonadTrace m
)
=> Env.Environment
-> Q.Query -- ^ SQL query
-> QualifiedTable
-> [ColumnInfo 'Postgres]
-> Maybe Int
-> MutationCTE
-> MutationOutput 'Postgres
-> Bool
-> [Q.PrepArg] -- ^ Prepared params
-> Maybe (RemoteJoins 'Postgres, MutationRemoteJoinCtx) -- ^ Remote joins context
-> m EncJSON
executeMutationOutputQuery env query prepArgs = \case
Nothing ->
runIdentity . Q.getRow
-- See Note [Prepared statements in Mutations]
<$> liftTx (Q.rawQE dmlTxErrorHandler query prepArgs False)
Just (remoteJoins, (httpManager, reqHeaders, userInfo)) ->
executeQueryWithRemoteJoins env httpManager reqHeaders userInfo query prepArgs remoteJoins
executeMutationOutputQuery env qt allCols preCalAffRows cte mutOutput strfyNum prepArgs maybeRJ = do
let queryTx :: Q.FromRes a => m a
queryTx = do
let selectWith = mkMutationOutputExp qt allCols preCalAffRows cte mutOutput strfyNum
query = Q.fromBuilder $ toSQL selectWith
-- See Note [Prepared statements in Mutations]
liftTx (Q.rawQE dmlTxErrorHandler query prepArgs False)
rawResponse <-
if checkPermissionRequired cte
then withCheckPermission $ Q.getRow <$> queryTx
else (runIdentity . Q.getRow) <$> queryTx
case maybeRJ of
Nothing -> pure $ encJFromLBS rawResponse
Just (remoteJoins, (httpManager, reqHeaders, userInfo)) ->
processRemoteJoins env httpManager reqHeaders userInfo rawResponse remoteJoins
mutateAndFetchCols
:: QualifiedTable
-> [ColumnInfo 'Postgres]
-> (S.CTE, DS.Seq Q.PrepArg)
-> (MutationCTE, DS.Seq Q.PrepArg)
-> Bool
-> Q.TxE QErr (MutateResp TxtEncodedPGVal)
mutateAndFetchCols qt cols (cte, p) strfyNum =
Q.getAltJ . runIdentity . Q.getRow
-- See Note [Prepared statements in Mutations]
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder sql) (toList p) False
mutateAndFetchCols qt cols (cte, p) strfyNum = do
let mutationTx :: Q.FromRes a => Q.TxE QErr a
mutationTx =
-- See Note [Prepared statements in Mutations]
Q.rawQE dmlTxErrorHandler sqlText (toList p) False
if checkPermissionRequired cte
then withCheckPermission $ (first Q.getAltJ . Q.getRow) <$> mutationTx
else (Q.getAltJ . runIdentity . Q.getRow) <$> mutationTx
where
aliasIdentifier = Identifier $ qualifiedObjectToText qt <> "__mutation_result"
tabFrom = FromIdentifier aliasIdentifier
@ -228,9 +251,12 @@ mutateAndFetchCols qt cols (cte, p) strfyNum =
selFlds = flip map cols $
\ci -> (fromPGCol $ pgiColumn ci, mkAnnColumnFieldAsText ci)
sql = toSQL selectWith
selectWith = S.SelectWith [(S.Alias aliasIdentifier, cte)] select
select = S.mkSelect {S.selExtr = [S.Extractor extrExp Nothing]}
sqlText = Q.fromBuilder $ toSQL selectWith
selectWith = S.SelectWith [(S.Alias aliasIdentifier, getMutationCTE cte)] select
select = S.mkSelect { S.selExtr = S.Extractor extrExp Nothing
: bool [] [S.Extractor checkErrExp Nothing] (checkPermissionRequired cte)
}
checkErrExp = mkCheckErrorExp aliasIdentifier
extrExp = S.applyJsonBuildObj
[ S.SELit "affected_rows", affRowsSel
, S.SELit "returning_columns", colSel

View File

@ -8,12 +8,14 @@ module Hasura.Backends.Postgres.Execute.RemoteJoin
, FieldPath(..)
, RemoteJoin(..)
, executeQueryWithRemoteJoins
, processRemoteJoins
) where
import Hasura.Prelude
import qualified Data.Aeson as A
import qualified Data.Aeson.Ordered as AO
import qualified Data.ByteString.Lazy as BL
import qualified Data.Environment as Env
import qualified Data.HashMap.Strict as Map
import qualified Data.HashMap.Strict.Extended as Map
@ -64,8 +66,26 @@ executeQueryWithRemoteJoins
-> RemoteJoins 'Postgres
-> m EncJSON
executeQueryWithRemoteJoins env manager reqHdrs userInfo q prepArgs rjs = do
-- Step 1: Perform the query on database and fetch the response
-- Perform the query on database and fetch the response
pgRes <- runIdentity . Q.getRow <$> Tracing.trace "Postgres" (liftTx (Q.rawQE dmlTxErrorHandler q prepArgs True))
-- Process remote joins in the response
processRemoteJoins env manager reqHdrs userInfo pgRes rjs
processRemoteJoins
:: ( HasVersion
, MonadTx m
, MonadIO m
, Tracing.MonadTrace m
)
=> Env.Environment
-> HTTP.Manager
-> [N.Header]
-> UserInfo
-> BL.ByteString
-> RemoteJoins 'Postgres
-> m EncJSON
processRemoteJoins env manager reqHdrs userInfo pgRes rjs = do
-- Step 1: Decode the given bytestring as a JSON value
jsonRes <- onLeft (AO.eitherDecode pgRes) (throw500 . T.pack)
-- Step 2: Traverse through the JSON obtained in above step and generate composite JSON value with remote joins
compositeJson <- traverseQueryResponseJSON rjMap jsonRes

View File

@ -2,17 +2,17 @@ module Hasura.Backends.Postgres.SQL.DML where
import Hasura.Prelude
import qualified Data.Aeson as J
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import qualified Text.Builder as TB
import qualified Data.Aeson as J
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import qualified Text.Builder as TB
import Data.String (fromString)
import Data.String (fromString)
import Data.Text.Extended
import Language.Haskell.TH.Syntax (Lift)
import Language.Haskell.TH.Syntax (Lift)
import Hasura.Backends.Postgres.SQL.Types
import Hasura.Incremental (Cacheable)
import Hasura.Incremental (Cacheable)
import Hasura.SQL.Types

View File

@ -1,5 +1,5 @@
module Hasura.Backends.Postgres.Translate.Delete
( mkDeleteCTE
( mkDelete
) where
import Hasura.Prelude
@ -12,12 +12,9 @@ import Hasura.Backends.Postgres.Translate.BoolExp
import Hasura.RQL.IR.Delete
import Hasura.RQL.Types
mkDeleteCTE
:: AnnDel 'Postgres -> S.CTE
mkDeleteCTE (AnnDel tn (fltr, wc) _ _) =
S.CTEDelete delete
mkDelete :: AnnDel 'Postgres -> S.SQLDelete
mkDelete (AnnDel tn (fltr, wc) _ _) =
S.SQLDelete tn Nothing tableFltr $ Just S.returningStar
where
delete = S.SQLDelete tn Nothing tableFltr $ Just S.returningStar
tableFltr = Just $ S.WhereFrag $
toSQLBoolExp (S.QualTable tn) $ andAnnBoolExps fltr wc

View File

@ -1,22 +1,23 @@
module Hasura.Backends.Postgres.Translate.Insert
( mkInsertCTE
, insertCheckExpr
, buildConflictClause
, toSQLConflict
, insertCheckConstraint
, insertOrUpdateCheckExpr
) where
import Hasura.Prelude
import qualified Data.HashSet as HS
import qualified Data.HashSet as HS
import Data.Text.Extended
import Instances.TH.Lift ()
import Instances.TH.Lift ()
import qualified Hasura.Backends.Postgres.SQL.DML as S
import qualified Hasura.Backends.Postgres.SQL.DML as S
import Hasura.Backends.Postgres.SQL.Types
import Hasura.Backends.Postgres.Translate.BoolExp
import Hasura.Backends.Postgres.Translate.Returning
import Hasura.RQL.DML.Internal
import Hasura.RQL.IR.Insert
import Hasura.RQL.Types
@ -32,11 +33,9 @@ mkInsertCTE (InsertQueryP1 tn cols vals conflict (insCheck, updCheck) _ _) =
. Just
. S.RetExp
$ [ S.selectStar
, S.Extractor
(insertOrUpdateCheckExpr tn conflict
(toSQLBool insCheck)
(fmap toSQLBool updCheck))
Nothing
, insertOrUpdateCheckExpr tn conflict
(toSQLBool insCheck)
(fmap toSQLBool updCheck)
]
toSQLBool = toSQLBoolExp $ S.QualTable tn
@ -117,28 +116,11 @@ buildConflictClause sessVarBldr tableInfo inpCols (OnConflict mTCol mTCons act)
validateInpCols inpCols updCols
return (updFiltr, preSet)
-- | Create an expression which will fail with a check constraint violation error
-- if the condition is not met on any of the inserted rows.
--
-- The resulting SQL will look something like this:
--
-- > INSERT INTO
-- > ...
-- > RETURNING
-- > *,
-- > CASE WHEN {cond}
-- > THEN NULL
-- > ELSE hdb_catalog.check_violation('insert check constraint failed')
-- > END
insertCheckExpr :: Text -> S.BoolExp -> S.SQLExp
insertCheckExpr errorMessage condExpr =
S.SECond condExpr S.SENull
(S.SEFunction
(S.FunctionExp
(QualifiedObject (SchemaName "hdb_catalog") (FunctionName "check_violation"))
(S.FunctionArgs [S.SELit errorMessage] mempty)
Nothing)
)
-- | Annotates the check constraint expression with @boolean@
-- (<check-condition>)::boolean
insertCheckConstraint :: S.BoolExp -> S.SQLExp
insertCheckConstraint boolExp =
S.SETyAnn (S.SEBool boolExp) S.boolTypeAnn
-- | When inserting data, we might need to also enforce the update
-- check condition, because we might fall back to an update via an
@ -153,15 +135,10 @@ insertCheckExpr errorMessage condExpr =
-- > RETURNING
-- > *,
-- > CASE WHEN xmax = 0
-- > THEN CASE WHEN {insert_cond}
-- > THEN NULL
-- > ELSE hdb_catalog.check_violation('insert check constraint failed')
-- > END
-- > ELSE CASE WHEN {update_cond}
-- > THEN NULL
-- > ELSE hdb_catalog.check_violation('update check constraint failed')
-- > END
-- > THEN {insert_cond}
-- > ELSE {update_cond}
-- > END
-- > AS "check__constraint"
--
-- See @https://stackoverflow.com/q/34762732@ for more information on the use of
-- the @xmax@ system column.
@ -170,15 +147,16 @@ insertOrUpdateCheckExpr
-> Maybe (ConflictClauseP1 'Postgres S.SQLExp)
-> S.BoolExp
-> Maybe S.BoolExp
-> S.SQLExp
-> S.Extractor
insertOrUpdateCheckExpr qt (Just _conflict) insCheck (Just updCheck) =
asCheckErrorExtractor $
S.SECond
(S.BECompare
S.SEQ
(S.SEQIdentifier (S.QIdentifier (S.mkQual qt) (Identifier "xmax")))
(S.SEUnsafe "0"))
(insertCheckExpr "insert check constraint failed" insCheck)
(insertCheckExpr "update check constraint failed" updCheck)
(insertCheckConstraint insCheck)
(insertCheckConstraint updCheck)
insertOrUpdateCheckExpr _ _ insCheck _ =
-- If we won't generate an ON CONFLICT clause, there is no point
-- in testing xmax. In particular, views don't provide the xmax
@ -188,4 +166,4 @@ insertOrUpdateCheckExpr _ _ insCheck _ =
--
-- Alternatively, if there is no update check constraint, we should
-- use the insert check constraint, for backwards compatibility.
insertCheckExpr "insert check constraint failed" insCheck
asCheckErrorExtractor $ insertCheckConstraint insCheck

View File

@ -1,5 +1,5 @@
module Hasura.Backends.Postgres.Translate.Mutation
( mkSelCTEFromColVals
( mkSelectExpFromColumnValues
)
where
@ -22,19 +22,18 @@ import Hasura.SQL.Types
-- For example, let's consider the table, `CREATE TABLE test (id serial primary key, name text not null, age int)`.
-- The generated values expression should be in order of columns;
-- `SELECT ("row"::table).* VALUES (1, 'Robert', 23) AS "row"`.
mkSelCTEFromColVals
mkSelectExpFromColumnValues
:: (MonadError QErr m)
=> QualifiedTable -> [ColumnInfo 'Postgres] -> [ColumnValues TxtEncodedPGVal] -> m S.CTE
mkSelCTEFromColVals qt allCols colVals =
S.CTESelect <$> case colVals of
[] -> return selNoRows
_ -> do
tuples <- mapM mkTupsFromColVal colVals
let fromItem = S.FIValues (S.ValuesExp tuples) (S.Alias rowAlias) Nothing
return S.mkSelect
{ S.selExtr = [extractor]
, S.selFrom = Just $ S.FromExp [fromItem]
}
=> QualifiedTable -> [ColumnInfo 'Postgres] -> [ColumnValues TxtEncodedPGVal] -> m S.Select
mkSelectExpFromColumnValues qt allCols = \case
[] -> return selNoRows
colVals -> do
tuples <- mapM mkTupsFromColVal colVals
let fromItem = S.FIValues (S.ValuesExp tuples) (S.Alias rowAlias) Nothing
return S.mkSelect
{ S.selExtr = [extractor]
, S.selFrom = Just $ S.FromExp [fromItem]
}
where
rowAlias = Identifier "row"
extractor = S.selectStar' $ S.QualifiedIdentifier rowAlias $ Just $ S.TypeAnn $ toSQLTxt qt

View File

@ -1,7 +1,10 @@
module Hasura.Backends.Postgres.Translate.Returning
( mkMutFldExp
, mkDefaultMutFlds
, mkCheckErrorExp
, mkMutationOutputExp
, checkConstraintIdentifier
, asCheckErrorExtractor
, checkRetCols
) where
@ -58,10 +61,7 @@ WITH "<table-name>__mutation_result_alias" AS (
(<insert-value-row>[..])
ON CONFLICT ON CONSTRAINT "<table-constraint-name>" DO NOTHING RETURNING *,
-- An extra column expression which performs the 'CHECK' validation
CASE
WHEN (<CHECK Condition>) THEN NULL
ELSE "hdb_catalog"."check_violation"('insert check constraint failed')
END
(<CHECK Condition>) AS "check__constraint"
),
"<table-name>__all_columns_alias" AS (
-- Only extract columns from mutated rows. Columns sorted by ordinal position so that
@ -70,7 +70,8 @@ WITH "<table-name>__mutation_result_alias" AS (
FROM
"<table-name>__mutation_result_alias"
)
<SELECT statement to generate mutation response using '<table-name>__all_columns_alias' as FROM>
<SELECT statement to generate mutation response using '<table-name>__all_columns_alias' as FROM
and bool_and("check__constraint") from "<table-name>__mutation_result_alias">
-}
-- | Generate mutation output expression with given mutation CTE statement.
@ -79,24 +80,27 @@ mkMutationOutputExp
:: QualifiedTable
-> [ColumnInfo 'Postgres]
-> Maybe Int
-> S.CTE
-> MutationCTE
-> MutationOutput 'Postgres
-> Bool
-> S.SelectWith
mkMutationOutputExp qt allCols preCalAffRows cte mutOutput strfyNum =
S.SelectWith [ (S.Alias mutationResultAlias, cte)
S.SelectWith [ (S.Alias mutationResultAlias, getMutationCTE cte)
, (S.Alias allColumnsAlias, allColumnsSelect)
] sel
where
mutationResultAlias = Identifier $ snakeCaseQualifiedObject qt <> "__mutation_result_alias"
allColumnsAlias = Identifier $ snakeCaseQualifiedObject qt <> "__all_columns_alias"
allColumnsSelect = S.CTESelect $ S.mkSelect
{ S.selExtr = map (S.mkExtr . pgiColumn) $ sortCols allCols
{ S.selExtr = map (S.mkExtr . pgiColumn) (sortCols allCols)
, S.selFrom = Just $ S.mkIdenFromExp mutationResultAlias
}
sel = S.mkSelect { S.selExtr = [S.Extractor extrExp Nothing] }
sel = S.mkSelect { S.selExtr = S.Extractor extrExp Nothing
: bool [] [S.Extractor checkErrorExp Nothing] (checkPermissionRequired cte)
}
where
checkErrorExp = mkCheckErrorExp mutationResultAlias
extrExp = case mutOutput of
MOutMultirowFields mutFlds ->
let jsonBuildObjArgs = flip concatMap mutFlds $
@ -111,6 +115,22 @@ mkMutationOutputExp qt allCols preCalAffRows cte mutOutput strfyNum =
in S.SESelect $ mkSQLSelect JASSingleObject $
AnnSelectG annFlds tabFrom tabPerm noSelectArgs strfyNum
mkCheckErrorExp :: IsIdentifier a => a -> S.SQLExp
mkCheckErrorExp alias =
let boolAndCheckConstraint =
S.handleIfNull (S.SEBool $ S.BELit True) $
S.SEFnApp "bool_and" [S.SEIdentifier checkConstraintIdentifier] Nothing
in S.SESelect $
S.mkSelect { S.selExtr = [S.Extractor boolAndCheckConstraint Nothing]
, S.selFrom = Just $ S.mkIdenFromExp alias
}
checkConstraintIdentifier :: Identifier
checkConstraintIdentifier = Identifier "check__constraint"
asCheckErrorExtractor :: S.SQLExp -> S.Extractor
asCheckErrorExtractor s =
S.Extractor s $ Just $ S.Alias checkConstraintIdentifier
checkRetCols
:: (UserInfoM m, QErrM m)

View File

@ -4,15 +4,16 @@ module Hasura.Backends.Postgres.Translate.Update
import Hasura.Prelude
import Instances.TH.Lift ()
import Instances.TH.Lift ()
import qualified Hasura.Backends.Postgres.SQL.DML as S
import qualified Hasura.Backends.Postgres.SQL.DML as S
import Hasura.Backends.Postgres.SQL.Types
import Hasura.Backends.Postgres.Translate.BoolExp
import Hasura.Backends.Postgres.Translate.Insert
import Hasura.Backends.Postgres.Translate.Returning
import Hasura.RQL.Instances ()
import Hasura.RQL.IR.Update
import Hasura.RQL.Instances ()
import Hasura.RQL.Types
@ -26,7 +27,7 @@ mkUpdateCTE (AnnUpd tn opExps (permFltr, wc) chk _ columnsInfo) =
. Just
. S.RetExp
$ [ S.selectStar
, S.Extractor (insertCheckExpr "update check constraint failed" checkExpr) Nothing
, asCheckErrorExtractor $ insertCheckConstraint checkExpr
]
setExp = S.SetExp $ map (expandOperator columnsInfo) opExps
tableFltr = Just $ S.WhereFrag tableFltrExpr

View File

@ -5,25 +5,24 @@ module Hasura.GraphQL.Execute.Insert
import Hasura.Prelude
import qualified Data.Aeson as J
import qualified Data.Environment as Env
import qualified Data.HashMap.Strict as Map
import qualified Data.Sequence as Seq
import qualified Data.Text as T
import qualified Database.PG.Query as Q
import qualified Data.Aeson as J
import qualified Data.Environment as Env
import qualified Data.HashMap.Strict as Map
import qualified Data.Sequence as Seq
import qualified Data.Text as T
import qualified Database.PG.Query as Q
import Data.Text.Extended
import qualified Hasura.Backends.Postgres.Execute.Mutation as RQL
import qualified Hasura.Backends.Postgres.Execute.RemoteJoin as RQL
import qualified Hasura.Backends.Postgres.SQL.DML as S
import qualified Hasura.Backends.Postgres.Translate.BoolExp as RQL
import qualified Hasura.Backends.Postgres.Translate.Insert as RQL
import qualified Hasura.Backends.Postgres.Translate.Mutation as RQL
import qualified Hasura.Backends.Postgres.Translate.Returning as RQL
import qualified Hasura.RQL.IR.Insert as RQL
import qualified Hasura.RQL.IR.Returning as RQL
import qualified Hasura.Tracing as Tracing
import qualified Hasura.Backends.Postgres.Execute.Mutation as RQL
import qualified Hasura.Backends.Postgres.Execute.RemoteJoin as RQL
import qualified Hasura.Backends.Postgres.SQL.DML as S
import qualified Hasura.Backends.Postgres.Translate.BoolExp as RQL
import qualified Hasura.Backends.Postgres.Translate.Insert as RQL
import qualified Hasura.Backends.Postgres.Translate.Mutation as RQL
import qualified Hasura.RQL.IR.Insert as RQL
import qualified Hasura.RQL.IR.Returning as RQL
import qualified Hasura.Tracing as Tracing
import Hasura.Backends.Postgres.Connection
import Hasura.Backends.Postgres.SQL.Types
@ -31,8 +30,7 @@ import Hasura.Backends.Postgres.SQL.Value
import Hasura.EncJSON
import Hasura.GraphQL.Schema.Insert
import Hasura.RQL.Types
import Hasura.SQL.Types
import Hasura.Server.Version (HasVersion)
import Hasura.Server.Version (HasVersion)
traverseAnnInsert
@ -133,11 +131,10 @@ insertMultipleObjects env multiObjIns additionalColumns remoteJoinCtx mutationOu
insertObject env singleObj additionalColumns remoteJoinCtx planVars stringifyNum
let affectedRows = sum $ map fst insertRequests
columnValues = mapMaybe snd insertRequests
selectExpr <- RQL.mkSelCTEFromColVals table columnInfos columnValues
selectExpr <- RQL.mkSelectExpFromColumnValues table columnInfos columnValues
let (mutOutputRJ, remoteJoins) = RQL.getRemoteJoinsMutationOutput mutationOutput
sqlQuery = Q.fromBuilder $ toSQL $
RQL.mkMutationOutputExp table columnInfos (Just affectedRows) selectExpr mutOutputRJ stringifyNum
RQL.executeMutationOutputQuery env sqlQuery [] $ (,remoteJoinCtx) <$> remoteJoins
RQL.executeMutationOutputQuery env table columnInfos (Just affectedRows) (RQL.MCSelectValues selectExpr)
mutOutputRJ stringifyNum [] $ (, remoteJoinCtx) <$> remoteJoins
insertObject
:: (HasVersion, MonadTx m, MonadIO m, Tracing.MonadTrace m)
@ -161,7 +158,8 @@ insertObject env singleObjIns additionalColumns remoteJoinCtx planVars stringify
cte <- mkInsertQ table onConflict finalInsCols defaultValues checkCond
MutateResp affRows colVals <- liftTx $ RQL.mutateAndFetchCols table allColumns (cte, planVars) stringifyNum
MutateResp affRows colVals <- liftTx $
RQL.mutateAndFetchCols table allColumns (RQL.MCCheckConstraint cte, planVars) stringifyNum
colValM <- asSingleObject colVals
arrRelAffRows <- bool (withArrRels colValM) (return 0) $ null arrayRels
@ -288,11 +286,9 @@ mkInsertQ table onConflictM insCols defVals (insCheck, updCheck) = do
. Just
$ S.RetExp
[ S.selectStar
, S.Extractor
(RQL.insertOrUpdateCheckExpr table onConflictM
(RQL.toSQLBoolExp (S.QualTable table) insCheck)
(fmap (RQL.toSQLBoolExp (S.QualTable table)) updCheck))
Nothing
, RQL.insertOrUpdateCheckExpr table onConflictM
(RQL.toSQLBoolExp (S.QualTable table) insCheck)
(fmap (RQL.toSQLBoolExp (S.QualTable table)) updCheck)
]
pure $ S.CTEInsert sqlInsert

View File

@ -2,14 +2,16 @@ module Hasura.RQL.IR.Returning where
import Hasura.Prelude
import qualified Data.Aeson as J
import qualified Data.HashMap.Strict.InsOrd as OMap
import qualified Data.Aeson as J
import qualified Data.HashMap.Strict.InsOrd as OMap
import Hasura.EncJSON
import Hasura.RQL.IR.Select
import Hasura.RQL.Types.Common
import Hasura.SQL.Backend
import qualified Hasura.Backends.Postgres.SQL.DML as S
data MutFldG (b :: Backend) v
= MCount
@ -78,3 +80,24 @@ hasNestedFld = \case
AFObjectRelation _ -> True
AFArrayRelation _ -> True
_ -> False
-- | The postgres common table expression (CTE) for mutation queries.
-- This CTE expression is used to generate mutation field output expression,
-- see Note [Mutation output expression].
data MutationCTE
= MCCheckConstraint !S.CTE -- ^ A Mutation with check constraint validation (Insert or Update)
| MCSelectValues !S.Select -- ^ A Select statement which emits mutated table rows
| MCDelete !S.SQLDelete -- ^ A Delete statement
deriving (Show, Eq)
getMutationCTE :: MutationCTE -> S.CTE
getMutationCTE = \case
MCCheckConstraint cte -> cte
MCSelectValues select -> S.CTESelect select
MCDelete delete -> S.CTEDelete delete
checkPermissionRequired :: MutationCTE -> Bool
checkPermissionRequired = \case
MCCheckConstraint _ -> True
MCSelectValues _ -> False
MCDelete _ -> False

View File

@ -6,7 +6,7 @@ response:
- extensions:
path: $.selectionSet.insert_computer.args.objects
code: permission-error
message: Check constraint violation. insert check constraint failed
message: check constraint of an insert/update permission has failed
headers:
X-Hasura-Role: developer
X-Hasura-Spec-Keys: |-

View File

@ -9,7 +9,7 @@ response:
- extensions:
path: $.selectionSet.insert_article.args.objects
code: permission-error
message: Check constraint violation. insert check constraint failed
message: check constraint of an insert/update permission has failed
query:
query: |
mutation insert_article {

View File

@ -6,7 +6,7 @@ response:
- extensions:
path: $.selectionSet.insert_computer.args.objects
code: permission-error
message: Check constraint violation. insert check constraint failed
message: check constraint of an insert/update permission has failed
headers:
X-Hasura-Role: seller
X-Hasura-Spec-Required-Keys: |-
@ -15,8 +15,8 @@ query:
variables:
spec:
processor: AMD
display: '16" FHD'
memory: '16 GB DDR4'
display: 16" FHD
memory: 16 GB DDR4
query: |
mutation insert_computer($spec: jsonb) {
insert_computer (

View File

@ -7,9 +7,9 @@ headers:
response:
errors:
- extensions:
path: "$.selectionSet.insert_account.args.objects"
path: $.selectionSet.insert_account.args.objects
code: permission-error
message: Check constraint violation. insert check constraint failed
message: check constraint of an insert/update permission has failed
query:
query: |
mutation{

View File

@ -5,7 +5,7 @@ response:
- extensions:
path: $
code: permission-error
message: Check constraint violation. update check constraint failed
message: check constraint of an insert/update permission has failed
headers:
X-Hasura-Role: user
X-Hasura-User-Id: '1'

View File

@ -5,13 +5,13 @@ headers:
X-Hasura-Role: student
response:
path: $.args
error: Check constraint violation. insert check constraint failed
error: check constraint of an insert/update permission has failed
code: permission-error
query:
type: insert
args:
table: author
objects:
- id: 5
name: Student 1
is_registered: false
- id: 5
name: Student 1
is_registered: false

View File

@ -6,7 +6,7 @@ headers:
X-Hasura-User-Id: '5'
response:
path: $.args
error: Check constraint violation. insert check constraint failed
error: check constraint of an insert/update permission has failed
code: permission-error
query:
type: insert
@ -19,4 +19,4 @@ query:
returning:
- id
- name
- is_registered
- is_registered

View File

@ -6,7 +6,7 @@ headers:
X-Hasura-User-Id: '5'
response:
path: $.args
error: Check constraint violation. insert check constraint failed
error: check constraint of an insert/update permission has failed
code: permission-error
query:
type: insert
@ -19,4 +19,4 @@ query:
returning:
- id
- name
- is_registered
- is_registered