mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
server: avoid raising mutation check constraint violation in pg procedure (#6123)
https://github.com/hasura/graphql-engine/pull/6123
This commit is contained in:
parent
01c4cd2ff3
commit
ca47c92f50
@ -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
|
:alt: validation using permission: title cannot be empty
|
||||||
|
|
||||||
.. tab:: CLI
|
.. tab:: CLI
|
||||||
|
|
||||||
You can add roles and permissions in the ``tables.yaml`` file inside the ``metadata`` directory:
|
You can add roles and permissions in the ``tables.yaml`` file inside the ``metadata`` directory:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
@ -345,7 +345,7 @@ If we try to insert an article with ``title = ""``, we will get a ``permission-e
|
|||||||
{
|
{
|
||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"message": "Check constraint violation. insert check constraint failed",
|
"message": "check constraint of an insert/update permission has failed",
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"path": "$.selectionSet.insert_article.args.objects",
|
"path": "$.selectionSet.insert_article.args.objects",
|
||||||
"code": "permission-error"
|
"code": "permission-error"
|
||||||
@ -449,7 +449,7 @@ will receive a ``permission-error`` :
|
|||||||
{
|
{
|
||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"message": "Check constraint violation. insert check constraint failed",
|
"message": "check constraint of an insert/update permission has failed",
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"path": "$.selectionSet.insert_article.args.objects",
|
"path": "$.selectionSet.insert_article.args.objects",
|
||||||
"code": "permission-error"
|
"code": "permission-error"
|
||||||
@ -545,7 +545,7 @@ we get the following error message:
|
|||||||
InsertAuthor(author: { name: "Thanos" }) {
|
InsertAuthor(author: { name: "Thanos" }) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:response:
|
:response:
|
||||||
{
|
{
|
||||||
"errors": [
|
"errors": [
|
||||||
|
@ -35,16 +35,16 @@ import Hasura.Backends.Postgres.Translate.Select
|
|||||||
import Hasura.Backends.Postgres.Translate.Update
|
import Hasura.Backends.Postgres.Translate.Update
|
||||||
import Hasura.EncJSON
|
import Hasura.EncJSON
|
||||||
import Hasura.RQL.DML.Internal
|
import Hasura.RQL.DML.Internal
|
||||||
|
import Hasura.RQL.Instances ()
|
||||||
import Hasura.RQL.IR.Delete
|
import Hasura.RQL.IR.Delete
|
||||||
import Hasura.RQL.IR.Insert
|
import Hasura.RQL.IR.Insert
|
||||||
import Hasura.RQL.IR.Returning
|
import Hasura.RQL.IR.Returning
|
||||||
import Hasura.RQL.IR.Select
|
import Hasura.RQL.IR.Select
|
||||||
import Hasura.RQL.IR.Update
|
import Hasura.RQL.IR.Update
|
||||||
import Hasura.RQL.Instances ()
|
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
import Hasura.SQL.Types
|
|
||||||
import Hasura.Server.Version (HasVersion)
|
import Hasura.Server.Version (HasVersion)
|
||||||
import Hasura.Session
|
import Hasura.Session
|
||||||
|
import Hasura.SQL.Types
|
||||||
|
|
||||||
|
|
||||||
type MutationRemoteJoinCtx = (HTTP.Manager, [N.Header], UserInfo)
|
type MutationRemoteJoinCtx = (HTTP.Manager, [N.Header], UserInfo)
|
||||||
@ -52,7 +52,7 @@ type MutationRemoteJoinCtx = (HTTP.Manager, [N.Header], UserInfo)
|
|||||||
data Mutation (b :: Backend)
|
data Mutation (b :: Backend)
|
||||||
= Mutation
|
= Mutation
|
||||||
{ _mTable :: !QualifiedTable
|
{ _mTable :: !QualifiedTable
|
||||||
, _mQuery :: !(S.CTE, DS.Seq Q.PrepArg)
|
, _mQuery :: !(MutationCTE, DS.Seq Q.PrepArg)
|
||||||
, _mOutput :: !(MutationOutput b)
|
, _mOutput :: !(MutationOutput b)
|
||||||
, _mCols :: ![ColumnInfo b]
|
, _mCols :: ![ColumnInfo b]
|
||||||
, _mRemoteJoins :: !(Maybe (RemoteJoins b, MutationRemoteJoinCtx))
|
, _mRemoteJoins :: !(Maybe (RemoteJoins b, MutationRemoteJoinCtx))
|
||||||
@ -62,7 +62,7 @@ data Mutation (b :: Backend)
|
|||||||
mkMutation
|
mkMutation
|
||||||
:: Maybe MutationRemoteJoinCtx
|
:: Maybe MutationRemoteJoinCtx
|
||||||
-> QualifiedTable
|
-> QualifiedTable
|
||||||
-> (S.CTE, DS.Seq Q.PrepArg)
|
-> (MutationCTE, DS.Seq Q.PrepArg)
|
||||||
-> MutationOutput 'Postgres
|
-> MutationOutput 'Postgres
|
||||||
-> [ColumnInfo 'Postgres]
|
-> [ColumnInfo 'Postgres]
|
||||||
-> Bool
|
-> Bool
|
||||||
@ -97,10 +97,7 @@ mutateAndReturn
|
|||||||
-> Mutation 'Postgres
|
-> Mutation 'Postgres
|
||||||
-> m EncJSON
|
-> m EncJSON
|
||||||
mutateAndReturn env (Mutation qt (cte, p) mutationOutput allCols remoteJoins strfyNum) =
|
mutateAndReturn env (Mutation qt (cte, p) mutationOutput allCols remoteJoins strfyNum) =
|
||||||
executeMutationOutputQuery env sqlQuery (toList p) remoteJoins
|
executeMutationOutputQuery env qt allCols Nothing cte mutationOutput strfyNum (toList p) remoteJoins
|
||||||
where
|
|
||||||
sqlQuery = Q.fromBuilder $ toSQL $
|
|
||||||
mkMutationOutputExp qt allCols Nothing cte mutationOutput strfyNum
|
|
||||||
|
|
||||||
|
|
||||||
execUpdateQuery
|
execUpdateQuery
|
||||||
@ -116,7 +113,7 @@ execUpdateQuery
|
|||||||
-> (AnnUpd 'Postgres, DS.Seq Q.PrepArg)
|
-> (AnnUpd 'Postgres, DS.Seq Q.PrepArg)
|
||||||
-> m EncJSON
|
-> m EncJSON
|
||||||
execUpdateQuery env strfyNum remoteJoinCtx (u, p) =
|
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
|
(uqp1Output u) (uqp1AllCols u) strfyNum
|
||||||
where
|
where
|
||||||
updateCTE = mkUpdateCTE u
|
updateCTE = mkUpdateCTE u
|
||||||
@ -134,10 +131,10 @@ execDeleteQuery
|
|||||||
-> (AnnDel 'Postgres, DS.Seq Q.PrepArg)
|
-> (AnnDel 'Postgres, DS.Seq Q.PrepArg)
|
||||||
-> m EncJSON
|
-> m EncJSON
|
||||||
execDeleteQuery env strfyNum remoteJoinCtx (u, p) =
|
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
|
(dqp1Output u) (dqp1AllCols u) strfyNum
|
||||||
where
|
where
|
||||||
deleteCTE = mkDeleteCTE u
|
delete = mkDelete u
|
||||||
|
|
||||||
execInsertQuery
|
execInsertQuery
|
||||||
:: ( HasVersion
|
:: ( HasVersion
|
||||||
@ -152,7 +149,7 @@ execInsertQuery
|
|||||||
-> m EncJSON
|
-> m EncJSON
|
||||||
execInsertQuery env strfyNum remoteJoinCtx (u, p) =
|
execInsertQuery env strfyNum remoteJoinCtx (u, p) =
|
||||||
runMutation env
|
runMutation env
|
||||||
$ mkMutation remoteJoinCtx (iqp1Table u) (insertCTE, p)
|
$ mkMutation remoteJoinCtx (iqp1Table u) (MCCheckConstraint insertCTE, p)
|
||||||
(iqp1Output u) (iqp1AllCols u) strfyNum
|
(iqp1Output u) (iqp1AllCols u) strfyNum
|
||||||
where
|
where
|
||||||
insertCTE = mkInsertCTE u
|
insertCTE = mkInsertCTE u
|
||||||
@ -186,41 +183,67 @@ mutateAndSel
|
|||||||
mutateAndSel env (Mutation qt q mutationOutput allCols remoteJoins strfyNum) = do
|
mutateAndSel env (Mutation qt q mutationOutput allCols remoteJoins strfyNum) = do
|
||||||
-- Perform mutation and fetch unique columns
|
-- Perform mutation and fetch unique columns
|
||||||
MutateResp _ columnVals <- liftTx $ mutateAndFetchCols qt allCols q strfyNum
|
MutateResp _ columnVals <- liftTx $ mutateAndFetchCols qt allCols q strfyNum
|
||||||
selCTE <- mkSelCTEFromColVals qt allCols columnVals
|
select <- mkSelectExpFromColumnValues qt allCols columnVals
|
||||||
let selWith = mkMutationOutputExp qt allCols Nothing selCTE mutationOutput strfyNum
|
|
||||||
-- Perform select query and fetch returning fields
|
-- 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
|
executeMutationOutputQuery
|
||||||
::
|
:: forall m.
|
||||||
( HasVersion
|
( HasVersion
|
||||||
, MonadTx m
|
, MonadTx m
|
||||||
, MonadIO m
|
, MonadIO m
|
||||||
, Tracing.MonadTrace m
|
, Tracing.MonadTrace m
|
||||||
)
|
)
|
||||||
=> Env.Environment
|
=> Env.Environment
|
||||||
-> Q.Query -- ^ SQL query
|
-> QualifiedTable
|
||||||
|
-> [ColumnInfo 'Postgres]
|
||||||
|
-> Maybe Int
|
||||||
|
-> MutationCTE
|
||||||
|
-> MutationOutput 'Postgres
|
||||||
|
-> Bool
|
||||||
-> [Q.PrepArg] -- ^ Prepared params
|
-> [Q.PrepArg] -- ^ Prepared params
|
||||||
-> Maybe (RemoteJoins 'Postgres, MutationRemoteJoinCtx) -- ^ Remote joins context
|
-> Maybe (RemoteJoins 'Postgres, MutationRemoteJoinCtx) -- ^ Remote joins context
|
||||||
-> m EncJSON
|
-> m EncJSON
|
||||||
executeMutationOutputQuery env query prepArgs = \case
|
executeMutationOutputQuery env qt allCols preCalAffRows cte mutOutput strfyNum prepArgs maybeRJ = do
|
||||||
Nothing ->
|
let queryTx :: Q.FromRes a => m a
|
||||||
runIdentity . Q.getRow
|
queryTx = do
|
||||||
-- See Note [Prepared statements in Mutations]
|
let selectWith = mkMutationOutputExp qt allCols preCalAffRows cte mutOutput strfyNum
|
||||||
<$> liftTx (Q.rawQE dmlTxErrorHandler query prepArgs False)
|
query = Q.fromBuilder $ toSQL selectWith
|
||||||
Just (remoteJoins, (httpManager, reqHeaders, userInfo)) ->
|
-- See Note [Prepared statements in Mutations]
|
||||||
executeQueryWithRemoteJoins env httpManager reqHeaders userInfo query prepArgs remoteJoins
|
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
|
mutateAndFetchCols
|
||||||
:: QualifiedTable
|
:: QualifiedTable
|
||||||
-> [ColumnInfo 'Postgres]
|
-> [ColumnInfo 'Postgres]
|
||||||
-> (S.CTE, DS.Seq Q.PrepArg)
|
-> (MutationCTE, DS.Seq Q.PrepArg)
|
||||||
-> Bool
|
-> Bool
|
||||||
-> Q.TxE QErr (MutateResp TxtEncodedPGVal)
|
-> Q.TxE QErr (MutateResp TxtEncodedPGVal)
|
||||||
mutateAndFetchCols qt cols (cte, p) strfyNum =
|
mutateAndFetchCols qt cols (cte, p) strfyNum = do
|
||||||
Q.getAltJ . runIdentity . Q.getRow
|
let mutationTx :: Q.FromRes a => Q.TxE QErr a
|
||||||
-- See Note [Prepared statements in Mutations]
|
mutationTx =
|
||||||
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder sql) (toList p) False
|
-- 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
|
where
|
||||||
aliasIdentifier = Identifier $ qualifiedObjectToText qt <> "__mutation_result"
|
aliasIdentifier = Identifier $ qualifiedObjectToText qt <> "__mutation_result"
|
||||||
tabFrom = FromIdentifier aliasIdentifier
|
tabFrom = FromIdentifier aliasIdentifier
|
||||||
@ -228,9 +251,12 @@ mutateAndFetchCols qt cols (cte, p) strfyNum =
|
|||||||
selFlds = flip map cols $
|
selFlds = flip map cols $
|
||||||
\ci -> (fromPGCol $ pgiColumn ci, mkAnnColumnFieldAsText ci)
|
\ci -> (fromPGCol $ pgiColumn ci, mkAnnColumnFieldAsText ci)
|
||||||
|
|
||||||
sql = toSQL selectWith
|
sqlText = Q.fromBuilder $ toSQL selectWith
|
||||||
selectWith = S.SelectWith [(S.Alias aliasIdentifier, cte)] select
|
selectWith = S.SelectWith [(S.Alias aliasIdentifier, getMutationCTE cte)] select
|
||||||
select = S.mkSelect {S.selExtr = [S.Extractor extrExp Nothing]}
|
select = S.mkSelect { S.selExtr = S.Extractor extrExp Nothing
|
||||||
|
: bool [] [S.Extractor checkErrExp Nothing] (checkPermissionRequired cte)
|
||||||
|
}
|
||||||
|
checkErrExp = mkCheckErrorExp aliasIdentifier
|
||||||
extrExp = S.applyJsonBuildObj
|
extrExp = S.applyJsonBuildObj
|
||||||
[ S.SELit "affected_rows", affRowsSel
|
[ S.SELit "affected_rows", affRowsSel
|
||||||
, S.SELit "returning_columns", colSel
|
, S.SELit "returning_columns", colSel
|
||||||
|
@ -8,12 +8,14 @@ module Hasura.Backends.Postgres.Execute.RemoteJoin
|
|||||||
, FieldPath(..)
|
, FieldPath(..)
|
||||||
, RemoteJoin(..)
|
, RemoteJoin(..)
|
||||||
, executeQueryWithRemoteJoins
|
, executeQueryWithRemoteJoins
|
||||||
|
, processRemoteJoins
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
|
|
||||||
import qualified Data.Aeson as A
|
import qualified Data.Aeson as A
|
||||||
import qualified Data.Aeson.Ordered as AO
|
import qualified Data.Aeson.Ordered as AO
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
import qualified Data.Environment as Env
|
import qualified Data.Environment as Env
|
||||||
import qualified Data.HashMap.Strict as Map
|
import qualified Data.HashMap.Strict as Map
|
||||||
import qualified Data.HashMap.Strict.Extended as Map
|
import qualified Data.HashMap.Strict.Extended as Map
|
||||||
@ -64,8 +66,26 @@ executeQueryWithRemoteJoins
|
|||||||
-> RemoteJoins 'Postgres
|
-> RemoteJoins 'Postgres
|
||||||
-> m EncJSON
|
-> m EncJSON
|
||||||
executeQueryWithRemoteJoins env manager reqHdrs userInfo q prepArgs rjs = do
|
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))
|
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)
|
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
|
-- Step 2: Traverse through the JSON obtained in above step and generate composite JSON value with remote joins
|
||||||
compositeJson <- traverseQueryResponseJSON rjMap jsonRes
|
compositeJson <- traverseQueryResponseJSON rjMap jsonRes
|
||||||
|
@ -2,17 +2,17 @@ module Hasura.Backends.Postgres.SQL.DML where
|
|||||||
|
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
|
|
||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
import qualified Data.HashMap.Strict as HM
|
import qualified Data.HashMap.Strict as HM
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Text.Builder as TB
|
import qualified Text.Builder as TB
|
||||||
|
|
||||||
import Data.String (fromString)
|
import Data.String (fromString)
|
||||||
import Data.Text.Extended
|
import Data.Text.Extended
|
||||||
import Language.Haskell.TH.Syntax (Lift)
|
import Language.Haskell.TH.Syntax (Lift)
|
||||||
|
|
||||||
import Hasura.Backends.Postgres.SQL.Types
|
import Hasura.Backends.Postgres.SQL.Types
|
||||||
import Hasura.Incremental (Cacheable)
|
import Hasura.Incremental (Cacheable)
|
||||||
import Hasura.SQL.Types
|
import Hasura.SQL.Types
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
module Hasura.Backends.Postgres.Translate.Delete
|
module Hasura.Backends.Postgres.Translate.Delete
|
||||||
( mkDeleteCTE
|
( mkDelete
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
@ -12,12 +12,9 @@ import Hasura.Backends.Postgres.Translate.BoolExp
|
|||||||
import Hasura.RQL.IR.Delete
|
import Hasura.RQL.IR.Delete
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
|
|
||||||
|
mkDelete :: AnnDel 'Postgres -> S.SQLDelete
|
||||||
mkDeleteCTE
|
mkDelete (AnnDel tn (fltr, wc) _ _) =
|
||||||
:: AnnDel 'Postgres -> S.CTE
|
S.SQLDelete tn Nothing tableFltr $ Just S.returningStar
|
||||||
mkDeleteCTE (AnnDel tn (fltr, wc) _ _) =
|
|
||||||
S.CTEDelete delete
|
|
||||||
where
|
where
|
||||||
delete = S.SQLDelete tn Nothing tableFltr $ Just S.returningStar
|
|
||||||
tableFltr = Just $ S.WhereFrag $
|
tableFltr = Just $ S.WhereFrag $
|
||||||
toSQLBoolExp (S.QualTable tn) $ andAnnBoolExps fltr wc
|
toSQLBoolExp (S.QualTable tn) $ andAnnBoolExps fltr wc
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
module Hasura.Backends.Postgres.Translate.Insert
|
module Hasura.Backends.Postgres.Translate.Insert
|
||||||
( mkInsertCTE
|
( mkInsertCTE
|
||||||
, insertCheckExpr
|
|
||||||
, buildConflictClause
|
, buildConflictClause
|
||||||
, toSQLConflict
|
, toSQLConflict
|
||||||
|
, insertCheckConstraint
|
||||||
, insertOrUpdateCheckExpr
|
, insertOrUpdateCheckExpr
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
|
|
||||||
import qualified Data.HashSet as HS
|
import qualified Data.HashSet as HS
|
||||||
|
|
||||||
import Data.Text.Extended
|
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.SQL.Types
|
||||||
import Hasura.Backends.Postgres.Translate.BoolExp
|
import Hasura.Backends.Postgres.Translate.BoolExp
|
||||||
|
import Hasura.Backends.Postgres.Translate.Returning
|
||||||
import Hasura.RQL.DML.Internal
|
import Hasura.RQL.DML.Internal
|
||||||
import Hasura.RQL.IR.Insert
|
import Hasura.RQL.IR.Insert
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
@ -32,11 +33,9 @@ mkInsertCTE (InsertQueryP1 tn cols vals conflict (insCheck, updCheck) _ _) =
|
|||||||
. Just
|
. Just
|
||||||
. S.RetExp
|
. S.RetExp
|
||||||
$ [ S.selectStar
|
$ [ S.selectStar
|
||||||
, S.Extractor
|
, insertOrUpdateCheckExpr tn conflict
|
||||||
(insertOrUpdateCheckExpr tn conflict
|
(toSQLBool insCheck)
|
||||||
(toSQLBool insCheck)
|
(fmap toSQLBool updCheck)
|
||||||
(fmap toSQLBool updCheck))
|
|
||||||
Nothing
|
|
||||||
]
|
]
|
||||||
toSQLBool = toSQLBoolExp $ S.QualTable tn
|
toSQLBool = toSQLBoolExp $ S.QualTable tn
|
||||||
|
|
||||||
@ -117,28 +116,11 @@ buildConflictClause sessVarBldr tableInfo inpCols (OnConflict mTCol mTCons act)
|
|||||||
validateInpCols inpCols updCols
|
validateInpCols inpCols updCols
|
||||||
return (updFiltr, preSet)
|
return (updFiltr, preSet)
|
||||||
|
|
||||||
-- | Create an expression which will fail with a check constraint violation error
|
-- | Annotates the check constraint expression with @boolean@
|
||||||
-- if the condition is not met on any of the inserted rows.
|
-- (<check-condition>)::boolean
|
||||||
--
|
insertCheckConstraint :: S.BoolExp -> S.SQLExp
|
||||||
-- The resulting SQL will look something like this:
|
insertCheckConstraint boolExp =
|
||||||
--
|
S.SETyAnn (S.SEBool boolExp) S.boolTypeAnn
|
||||||
-- > 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)
|
|
||||||
)
|
|
||||||
|
|
||||||
-- | When inserting data, we might need to also enforce the update
|
-- | When inserting data, we might need to also enforce the update
|
||||||
-- check condition, because we might fall back to an update via an
|
-- check condition, because we might fall back to an update via an
|
||||||
@ -153,15 +135,10 @@ insertCheckExpr errorMessage condExpr =
|
|||||||
-- > RETURNING
|
-- > RETURNING
|
||||||
-- > *,
|
-- > *,
|
||||||
-- > CASE WHEN xmax = 0
|
-- > CASE WHEN xmax = 0
|
||||||
-- > THEN CASE WHEN {insert_cond}
|
-- > THEN {insert_cond}
|
||||||
-- > THEN NULL
|
-- > ELSE {update_cond}
|
||||||
-- > 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
|
|
||||||
-- > END
|
-- > END
|
||||||
|
-- > AS "check__constraint"
|
||||||
--
|
--
|
||||||
-- See @https://stackoverflow.com/q/34762732@ for more information on the use of
|
-- See @https://stackoverflow.com/q/34762732@ for more information on the use of
|
||||||
-- the @xmax@ system column.
|
-- the @xmax@ system column.
|
||||||
@ -170,15 +147,16 @@ insertOrUpdateCheckExpr
|
|||||||
-> Maybe (ConflictClauseP1 'Postgres S.SQLExp)
|
-> Maybe (ConflictClauseP1 'Postgres S.SQLExp)
|
||||||
-> S.BoolExp
|
-> S.BoolExp
|
||||||
-> Maybe S.BoolExp
|
-> Maybe S.BoolExp
|
||||||
-> S.SQLExp
|
-> S.Extractor
|
||||||
insertOrUpdateCheckExpr qt (Just _conflict) insCheck (Just updCheck) =
|
insertOrUpdateCheckExpr qt (Just _conflict) insCheck (Just updCheck) =
|
||||||
|
asCheckErrorExtractor $
|
||||||
S.SECond
|
S.SECond
|
||||||
(S.BECompare
|
(S.BECompare
|
||||||
S.SEQ
|
S.SEQ
|
||||||
(S.SEQIdentifier (S.QIdentifier (S.mkQual qt) (Identifier "xmax")))
|
(S.SEQIdentifier (S.QIdentifier (S.mkQual qt) (Identifier "xmax")))
|
||||||
(S.SEUnsafe "0"))
|
(S.SEUnsafe "0"))
|
||||||
(insertCheckExpr "insert check constraint failed" insCheck)
|
(insertCheckConstraint insCheck)
|
||||||
(insertCheckExpr "update check constraint failed" updCheck)
|
(insertCheckConstraint updCheck)
|
||||||
insertOrUpdateCheckExpr _ _ insCheck _ =
|
insertOrUpdateCheckExpr _ _ insCheck _ =
|
||||||
-- If we won't generate an ON CONFLICT clause, there is no point
|
-- If we won't generate an ON CONFLICT clause, there is no point
|
||||||
-- in testing xmax. In particular, views don't provide the xmax
|
-- 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
|
-- Alternatively, if there is no update check constraint, we should
|
||||||
-- use the insert check constraint, for backwards compatibility.
|
-- use the insert check constraint, for backwards compatibility.
|
||||||
insertCheckExpr "insert check constraint failed" insCheck
|
asCheckErrorExtractor $ insertCheckConstraint insCheck
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
module Hasura.Backends.Postgres.Translate.Mutation
|
module Hasura.Backends.Postgres.Translate.Mutation
|
||||||
( mkSelCTEFromColVals
|
( mkSelectExpFromColumnValues
|
||||||
)
|
)
|
||||||
where
|
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)`.
|
-- 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;
|
-- The generated values expression should be in order of columns;
|
||||||
-- `SELECT ("row"::table).* VALUES (1, 'Robert', 23) AS "row"`.
|
-- `SELECT ("row"::table).* VALUES (1, 'Robert', 23) AS "row"`.
|
||||||
mkSelCTEFromColVals
|
mkSelectExpFromColumnValues
|
||||||
:: (MonadError QErr m)
|
:: (MonadError QErr m)
|
||||||
=> QualifiedTable -> [ColumnInfo 'Postgres] -> [ColumnValues TxtEncodedPGVal] -> m S.CTE
|
=> QualifiedTable -> [ColumnInfo 'Postgres] -> [ColumnValues TxtEncodedPGVal] -> m S.Select
|
||||||
mkSelCTEFromColVals qt allCols colVals =
|
mkSelectExpFromColumnValues qt allCols = \case
|
||||||
S.CTESelect <$> case colVals of
|
[] -> return selNoRows
|
||||||
[] -> return selNoRows
|
colVals -> do
|
||||||
_ -> do
|
tuples <- mapM mkTupsFromColVal colVals
|
||||||
tuples <- mapM mkTupsFromColVal colVals
|
let fromItem = S.FIValues (S.ValuesExp tuples) (S.Alias rowAlias) Nothing
|
||||||
let fromItem = S.FIValues (S.ValuesExp tuples) (S.Alias rowAlias) Nothing
|
return S.mkSelect
|
||||||
return S.mkSelect
|
{ S.selExtr = [extractor]
|
||||||
{ S.selExtr = [extractor]
|
, S.selFrom = Just $ S.FromExp [fromItem]
|
||||||
, S.selFrom = Just $ S.FromExp [fromItem]
|
}
|
||||||
}
|
|
||||||
where
|
where
|
||||||
rowAlias = Identifier "row"
|
rowAlias = Identifier "row"
|
||||||
extractor = S.selectStar' $ S.QualifiedIdentifier rowAlias $ Just $ S.TypeAnn $ toSQLTxt qt
|
extractor = S.selectStar' $ S.QualifiedIdentifier rowAlias $ Just $ S.TypeAnn $ toSQLTxt qt
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
module Hasura.Backends.Postgres.Translate.Returning
|
module Hasura.Backends.Postgres.Translate.Returning
|
||||||
( mkMutFldExp
|
( mkMutFldExp
|
||||||
, mkDefaultMutFlds
|
, mkDefaultMutFlds
|
||||||
|
, mkCheckErrorExp
|
||||||
, mkMutationOutputExp
|
, mkMutationOutputExp
|
||||||
|
, checkConstraintIdentifier
|
||||||
|
, asCheckErrorExtractor
|
||||||
, checkRetCols
|
, checkRetCols
|
||||||
) where
|
) where
|
||||||
|
|
||||||
@ -58,10 +61,7 @@ WITH "<table-name>__mutation_result_alias" AS (
|
|||||||
(<insert-value-row>[..])
|
(<insert-value-row>[..])
|
||||||
ON CONFLICT ON CONSTRAINT "<table-constraint-name>" DO NOTHING RETURNING *,
|
ON CONFLICT ON CONSTRAINT "<table-constraint-name>" DO NOTHING RETURNING *,
|
||||||
-- An extra column expression which performs the 'CHECK' validation
|
-- An extra column expression which performs the 'CHECK' validation
|
||||||
CASE
|
(<CHECK Condition>) AS "check__constraint"
|
||||||
WHEN (<CHECK Condition>) THEN NULL
|
|
||||||
ELSE "hdb_catalog"."check_violation"('insert check constraint failed')
|
|
||||||
END
|
|
||||||
),
|
),
|
||||||
"<table-name>__all_columns_alias" AS (
|
"<table-name>__all_columns_alias" AS (
|
||||||
-- Only extract columns from mutated rows. Columns sorted by ordinal position so that
|
-- 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
|
FROM
|
||||||
"<table-name>__mutation_result_alias"
|
"<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.
|
-- | Generate mutation output expression with given mutation CTE statement.
|
||||||
@ -79,24 +80,27 @@ mkMutationOutputExp
|
|||||||
:: QualifiedTable
|
:: QualifiedTable
|
||||||
-> [ColumnInfo 'Postgres]
|
-> [ColumnInfo 'Postgres]
|
||||||
-> Maybe Int
|
-> Maybe Int
|
||||||
-> S.CTE
|
-> MutationCTE
|
||||||
-> MutationOutput 'Postgres
|
-> MutationOutput 'Postgres
|
||||||
-> Bool
|
-> Bool
|
||||||
-> S.SelectWith
|
-> S.SelectWith
|
||||||
mkMutationOutputExp qt allCols preCalAffRows cte mutOutput strfyNum =
|
mkMutationOutputExp qt allCols preCalAffRows cte mutOutput strfyNum =
|
||||||
S.SelectWith [ (S.Alias mutationResultAlias, cte)
|
S.SelectWith [ (S.Alias mutationResultAlias, getMutationCTE cte)
|
||||||
, (S.Alias allColumnsAlias, allColumnsSelect)
|
, (S.Alias allColumnsAlias, allColumnsSelect)
|
||||||
] sel
|
] sel
|
||||||
where
|
where
|
||||||
mutationResultAlias = Identifier $ snakeCaseQualifiedObject qt <> "__mutation_result_alias"
|
mutationResultAlias = Identifier $ snakeCaseQualifiedObject qt <> "__mutation_result_alias"
|
||||||
allColumnsAlias = Identifier $ snakeCaseQualifiedObject qt <> "__all_columns_alias"
|
allColumnsAlias = Identifier $ snakeCaseQualifiedObject qt <> "__all_columns_alias"
|
||||||
allColumnsSelect = S.CTESelect $ S.mkSelect
|
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
|
, 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
|
where
|
||||||
|
checkErrorExp = mkCheckErrorExp mutationResultAlias
|
||||||
extrExp = case mutOutput of
|
extrExp = case mutOutput of
|
||||||
MOutMultirowFields mutFlds ->
|
MOutMultirowFields mutFlds ->
|
||||||
let jsonBuildObjArgs = flip concatMap mutFlds $
|
let jsonBuildObjArgs = flip concatMap mutFlds $
|
||||||
@ -111,6 +115,22 @@ mkMutationOutputExp qt allCols preCalAffRows cte mutOutput strfyNum =
|
|||||||
in S.SESelect $ mkSQLSelect JASSingleObject $
|
in S.SESelect $ mkSQLSelect JASSingleObject $
|
||||||
AnnSelectG annFlds tabFrom tabPerm noSelectArgs strfyNum
|
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
|
checkRetCols
|
||||||
:: (UserInfoM m, QErrM m)
|
:: (UserInfoM m, QErrM m)
|
||||||
|
@ -4,15 +4,16 @@ module Hasura.Backends.Postgres.Translate.Update
|
|||||||
|
|
||||||
import Hasura.Prelude
|
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.SQL.Types
|
||||||
import Hasura.Backends.Postgres.Translate.BoolExp
|
import Hasura.Backends.Postgres.Translate.BoolExp
|
||||||
import Hasura.Backends.Postgres.Translate.Insert
|
import Hasura.Backends.Postgres.Translate.Insert
|
||||||
|
import Hasura.Backends.Postgres.Translate.Returning
|
||||||
|
import Hasura.RQL.Instances ()
|
||||||
import Hasura.RQL.IR.Update
|
import Hasura.RQL.IR.Update
|
||||||
import Hasura.RQL.Instances ()
|
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ mkUpdateCTE (AnnUpd tn opExps (permFltr, wc) chk _ columnsInfo) =
|
|||||||
. Just
|
. Just
|
||||||
. S.RetExp
|
. S.RetExp
|
||||||
$ [ S.selectStar
|
$ [ S.selectStar
|
||||||
, S.Extractor (insertCheckExpr "update check constraint failed" checkExpr) Nothing
|
, asCheckErrorExtractor $ insertCheckConstraint checkExpr
|
||||||
]
|
]
|
||||||
setExp = S.SetExp $ map (expandOperator columnsInfo) opExps
|
setExp = S.SetExp $ map (expandOperator columnsInfo) opExps
|
||||||
tableFltr = Just $ S.WhereFrag tableFltrExpr
|
tableFltr = Just $ S.WhereFrag tableFltrExpr
|
||||||
|
@ -5,25 +5,24 @@ module Hasura.GraphQL.Execute.Insert
|
|||||||
|
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
|
|
||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
import qualified Data.Environment as Env
|
import qualified Data.Environment as Env
|
||||||
import qualified Data.HashMap.Strict as Map
|
import qualified Data.HashMap.Strict as Map
|
||||||
import qualified Data.Sequence as Seq
|
import qualified Data.Sequence as Seq
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Database.PG.Query as Q
|
import qualified Database.PG.Query as Q
|
||||||
|
|
||||||
import Data.Text.Extended
|
import Data.Text.Extended
|
||||||
|
|
||||||
import qualified Hasura.Backends.Postgres.Execute.Mutation as RQL
|
import qualified Hasura.Backends.Postgres.Execute.Mutation as RQL
|
||||||
import qualified Hasura.Backends.Postgres.Execute.RemoteJoin 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.SQL.DML as S
|
||||||
import qualified Hasura.Backends.Postgres.Translate.BoolExp as RQL
|
import qualified Hasura.Backends.Postgres.Translate.BoolExp as RQL
|
||||||
import qualified Hasura.Backends.Postgres.Translate.Insert 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.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.Insert as RQL
|
import qualified Hasura.RQL.IR.Returning as RQL
|
||||||
import qualified Hasura.RQL.IR.Returning as RQL
|
import qualified Hasura.Tracing as Tracing
|
||||||
import qualified Hasura.Tracing as Tracing
|
|
||||||
|
|
||||||
import Hasura.Backends.Postgres.Connection
|
import Hasura.Backends.Postgres.Connection
|
||||||
import Hasura.Backends.Postgres.SQL.Types
|
import Hasura.Backends.Postgres.SQL.Types
|
||||||
@ -31,8 +30,7 @@ import Hasura.Backends.Postgres.SQL.Value
|
|||||||
import Hasura.EncJSON
|
import Hasura.EncJSON
|
||||||
import Hasura.GraphQL.Schema.Insert
|
import Hasura.GraphQL.Schema.Insert
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
import Hasura.SQL.Types
|
import Hasura.Server.Version (HasVersion)
|
||||||
import Hasura.Server.Version (HasVersion)
|
|
||||||
|
|
||||||
|
|
||||||
traverseAnnInsert
|
traverseAnnInsert
|
||||||
@ -133,11 +131,10 @@ insertMultipleObjects env multiObjIns additionalColumns remoteJoinCtx mutationOu
|
|||||||
insertObject env singleObj additionalColumns remoteJoinCtx planVars stringifyNum
|
insertObject env singleObj additionalColumns remoteJoinCtx planVars stringifyNum
|
||||||
let affectedRows = sum $ map fst insertRequests
|
let affectedRows = sum $ map fst insertRequests
|
||||||
columnValues = mapMaybe snd insertRequests
|
columnValues = mapMaybe snd insertRequests
|
||||||
selectExpr <- RQL.mkSelCTEFromColVals table columnInfos columnValues
|
selectExpr <- RQL.mkSelectExpFromColumnValues table columnInfos columnValues
|
||||||
let (mutOutputRJ, remoteJoins) = RQL.getRemoteJoinsMutationOutput mutationOutput
|
let (mutOutputRJ, remoteJoins) = RQL.getRemoteJoinsMutationOutput mutationOutput
|
||||||
sqlQuery = Q.fromBuilder $ toSQL $
|
RQL.executeMutationOutputQuery env table columnInfos (Just affectedRows) (RQL.MCSelectValues selectExpr)
|
||||||
RQL.mkMutationOutputExp table columnInfos (Just affectedRows) selectExpr mutOutputRJ stringifyNum
|
mutOutputRJ stringifyNum [] $ (, remoteJoinCtx) <$> remoteJoins
|
||||||
RQL.executeMutationOutputQuery env sqlQuery [] $ (,remoteJoinCtx) <$> remoteJoins
|
|
||||||
|
|
||||||
insertObject
|
insertObject
|
||||||
:: (HasVersion, MonadTx m, MonadIO m, Tracing.MonadTrace m)
|
:: (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
|
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
|
colValM <- asSingleObject colVals
|
||||||
|
|
||||||
arrRelAffRows <- bool (withArrRels colValM) (return 0) $ null arrayRels
|
arrRelAffRows <- bool (withArrRels colValM) (return 0) $ null arrayRels
|
||||||
@ -288,11 +286,9 @@ mkInsertQ table onConflictM insCols defVals (insCheck, updCheck) = do
|
|||||||
. Just
|
. Just
|
||||||
$ S.RetExp
|
$ S.RetExp
|
||||||
[ S.selectStar
|
[ S.selectStar
|
||||||
, S.Extractor
|
, RQL.insertOrUpdateCheckExpr table onConflictM
|
||||||
(RQL.insertOrUpdateCheckExpr table onConflictM
|
(RQL.toSQLBoolExp (S.QualTable table) insCheck)
|
||||||
(RQL.toSQLBoolExp (S.QualTable table) insCheck)
|
(fmap (RQL.toSQLBoolExp (S.QualTable table)) updCheck)
|
||||||
(fmap (RQL.toSQLBoolExp (S.QualTable table)) updCheck))
|
|
||||||
Nothing
|
|
||||||
]
|
]
|
||||||
pure $ S.CTEInsert sqlInsert
|
pure $ S.CTEInsert sqlInsert
|
||||||
|
|
||||||
|
@ -2,14 +2,16 @@ module Hasura.RQL.IR.Returning where
|
|||||||
|
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
|
|
||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
import qualified Data.HashMap.Strict.InsOrd as OMap
|
import qualified Data.HashMap.Strict.InsOrd as OMap
|
||||||
|
|
||||||
import Hasura.EncJSON
|
import Hasura.EncJSON
|
||||||
import Hasura.RQL.IR.Select
|
import Hasura.RQL.IR.Select
|
||||||
import Hasura.RQL.Types.Common
|
import Hasura.RQL.Types.Common
|
||||||
import Hasura.SQL.Backend
|
import Hasura.SQL.Backend
|
||||||
|
|
||||||
|
import qualified Hasura.Backends.Postgres.SQL.DML as S
|
||||||
|
|
||||||
|
|
||||||
data MutFldG (b :: Backend) v
|
data MutFldG (b :: Backend) v
|
||||||
= MCount
|
= MCount
|
||||||
@ -78,3 +80,24 @@ hasNestedFld = \case
|
|||||||
AFObjectRelation _ -> True
|
AFObjectRelation _ -> True
|
||||||
AFArrayRelation _ -> True
|
AFArrayRelation _ -> True
|
||||||
_ -> False
|
_ -> 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
|
||||||
|
@ -6,7 +6,7 @@ response:
|
|||||||
- extensions:
|
- extensions:
|
||||||
path: $.selectionSet.insert_computer.args.objects
|
path: $.selectionSet.insert_computer.args.objects
|
||||||
code: permission-error
|
code: permission-error
|
||||||
message: Check constraint violation. insert check constraint failed
|
message: check constraint of an insert/update permission has failed
|
||||||
headers:
|
headers:
|
||||||
X-Hasura-Role: developer
|
X-Hasura-Role: developer
|
||||||
X-Hasura-Spec-Keys: |-
|
X-Hasura-Spec-Keys: |-
|
||||||
|
@ -9,7 +9,7 @@ response:
|
|||||||
- extensions:
|
- extensions:
|
||||||
path: $.selectionSet.insert_article.args.objects
|
path: $.selectionSet.insert_article.args.objects
|
||||||
code: permission-error
|
code: permission-error
|
||||||
message: Check constraint violation. insert check constraint failed
|
message: check constraint of an insert/update permission has failed
|
||||||
query:
|
query:
|
||||||
query: |
|
query: |
|
||||||
mutation insert_article {
|
mutation insert_article {
|
||||||
|
@ -6,7 +6,7 @@ response:
|
|||||||
- extensions:
|
- extensions:
|
||||||
path: $.selectionSet.insert_computer.args.objects
|
path: $.selectionSet.insert_computer.args.objects
|
||||||
code: permission-error
|
code: permission-error
|
||||||
message: Check constraint violation. insert check constraint failed
|
message: check constraint of an insert/update permission has failed
|
||||||
headers:
|
headers:
|
||||||
X-Hasura-Role: seller
|
X-Hasura-Role: seller
|
||||||
X-Hasura-Spec-Required-Keys: |-
|
X-Hasura-Spec-Required-Keys: |-
|
||||||
@ -15,8 +15,8 @@ query:
|
|||||||
variables:
|
variables:
|
||||||
spec:
|
spec:
|
||||||
processor: AMD
|
processor: AMD
|
||||||
display: '16" FHD'
|
display: 16" FHD
|
||||||
memory: '16 GB DDR4'
|
memory: 16 GB DDR4
|
||||||
query: |
|
query: |
|
||||||
mutation insert_computer($spec: jsonb) {
|
mutation insert_computer($spec: jsonb) {
|
||||||
insert_computer (
|
insert_computer (
|
||||||
|
@ -7,9 +7,9 @@ headers:
|
|||||||
response:
|
response:
|
||||||
errors:
|
errors:
|
||||||
- extensions:
|
- extensions:
|
||||||
path: "$.selectionSet.insert_account.args.objects"
|
path: $.selectionSet.insert_account.args.objects
|
||||||
code: permission-error
|
code: permission-error
|
||||||
message: Check constraint violation. insert check constraint failed
|
message: check constraint of an insert/update permission has failed
|
||||||
query:
|
query:
|
||||||
query: |
|
query: |
|
||||||
mutation{
|
mutation{
|
||||||
|
@ -5,7 +5,7 @@ response:
|
|||||||
- extensions:
|
- extensions:
|
||||||
path: $
|
path: $
|
||||||
code: permission-error
|
code: permission-error
|
||||||
message: Check constraint violation. update check constraint failed
|
message: check constraint of an insert/update permission has failed
|
||||||
headers:
|
headers:
|
||||||
X-Hasura-Role: user
|
X-Hasura-Role: user
|
||||||
X-Hasura-User-Id: '1'
|
X-Hasura-User-Id: '1'
|
||||||
|
@ -5,13 +5,13 @@ headers:
|
|||||||
X-Hasura-Role: student
|
X-Hasura-Role: student
|
||||||
response:
|
response:
|
||||||
path: $.args
|
path: $.args
|
||||||
error: Check constraint violation. insert check constraint failed
|
error: check constraint of an insert/update permission has failed
|
||||||
code: permission-error
|
code: permission-error
|
||||||
query:
|
query:
|
||||||
type: insert
|
type: insert
|
||||||
args:
|
args:
|
||||||
table: author
|
table: author
|
||||||
objects:
|
objects:
|
||||||
- id: 5
|
- id: 5
|
||||||
name: Student 1
|
name: Student 1
|
||||||
is_registered: false
|
is_registered: false
|
||||||
|
@ -6,7 +6,7 @@ headers:
|
|||||||
X-Hasura-User-Id: '5'
|
X-Hasura-User-Id: '5'
|
||||||
response:
|
response:
|
||||||
path: $.args
|
path: $.args
|
||||||
error: Check constraint violation. insert check constraint failed
|
error: check constraint of an insert/update permission has failed
|
||||||
code: permission-error
|
code: permission-error
|
||||||
query:
|
query:
|
||||||
type: insert
|
type: insert
|
||||||
@ -19,4 +19,4 @@ query:
|
|||||||
returning:
|
returning:
|
||||||
- id
|
- id
|
||||||
- name
|
- name
|
||||||
- is_registered
|
- is_registered
|
||||||
|
@ -6,7 +6,7 @@ headers:
|
|||||||
X-Hasura-User-Id: '5'
|
X-Hasura-User-Id: '5'
|
||||||
response:
|
response:
|
||||||
path: $.args
|
path: $.args
|
||||||
error: Check constraint violation. insert check constraint failed
|
error: check constraint of an insert/update permission has failed
|
||||||
code: permission-error
|
code: permission-error
|
||||||
query:
|
query:
|
||||||
type: insert
|
type: insert
|
||||||
@ -19,4 +19,4 @@ query:
|
|||||||
returning:
|
returning:
|
||||||
- id
|
- id
|
||||||
- name
|
- name
|
||||||
- is_registered
|
- is_registered
|
||||||
|
Loading…
Reference in New Issue
Block a user