apply update permissions for upsert mutations (#628)

This commit is contained in:
Rakesh Emmadi 2018-12-15 21:40:29 +05:30 committed by Vamshi Surabhi
parent 58cfbed22e
commit 3026c49087
49 changed files with 461 additions and 425 deletions

View File

@ -14,7 +14,6 @@
insert_permissions:
- comment: null
permission:
allow_upsert: true
check:
user_id:
_eq: X-HASURA-USER-ID

View File

@ -14,7 +14,6 @@
insert_permissions:
- comment: null
permission:
allow_upsert: true
check:
user_id:
_eq: X-HASURA-USER-ID

View File

@ -37,26 +37,6 @@ export const permNoCheck = (tableName, query, first) => {
savePermission();
// Validate
validatePermission(tableName, 'role0', query, 'none', 'success', null, true);
// Do not allow users to make upset queries in case of Insert
if (query === 'insert') {
// Reopen insert permission
cy.get(getElementFromAlias('role0-insert')).click();
cy.get('span')
.contains('upsert queries')
.click();
// Save
savePermission();
// Validate
validatePermission(
tableName,
'role0',
query,
'none',
'success',
null,
false
);
}
};
export const permCustomCheck = (tableName, query) => {

View File

@ -143,11 +143,10 @@ export const validateInsert = (tableName, rows) => {
// ******************* Permissiosn Validator ****************
const compareChecks = (permObj, check, query, columns, allowUpsert) => {
const compareChecks = (permObj, check, query, columns) => {
if (check === 'none') {
if (query === 'insert') {
expect(Object.keys(permObj.check).length === 0).to.be.true;
expect(permObj.allow_upsert === allowUpsert).to.be.true;
} else {
expect(Object.keys(permObj.filter).length === 0).to.be.true;
if (query === 'select' || query === 'update') {
@ -159,7 +158,6 @@ const compareChecks = (permObj, check, query, columns, allowUpsert) => {
} else if (query === 'insert') {
// eslint-disable-line no-lonely-if
expect(permObj.check[getColName(0)]._eq === '1').to.be.true; // eslint-dsable-line eqeqeq
expect(permObj.allow_upsert === allowUpsert).to.be.true;
} else {
expect(permObj.filter[getColName(0)]._eq === '1').to.be.true;
if (query === 'select' || query === 'update') {
@ -176,8 +174,7 @@ const handlePermValidationResponse = (
query,
check,
result,
columns,
allowUpsert
columns
) => {
const rolePerms = tableSchema.permissions.find(
permission => permission.role_name === role
@ -185,7 +182,7 @@ const handlePermValidationResponse = (
if (rolePerms) {
const permObj = rolePerms.permissions[query];
if (permObj) {
compareChecks(permObj, check, query, columns, allowUpsert, result);
compareChecks(permObj, check, query, columns);
} else {
// this block can be reached only if the permission doesn't exist (failure case)
expect(result === 'failure').to.be.true;
@ -202,8 +199,7 @@ export const validatePermission = (
query,
check,
result,
columns,
allowUpsert
columns
) => {
const reqBody = {
type: 'select',
@ -230,8 +226,7 @@ export const validatePermission = (
query,
check,
result,
columns,
allowUpsert
columns
);
});
};

View File

@ -280,7 +280,6 @@ hr
margin-right: 20px;
}
.form_permission_insert_set_wrapper {
margin-left: 5px;
.permission_insert_set_wrapper {
.configure_insert_set_checkbox {
position: relative;

View File

@ -61,6 +61,7 @@ const getQueriesWithPermColumns = insert => {
}
return queries;
};
const permChangeTypes = {
save: 'update',
delete: 'delete',

View File

@ -204,16 +204,22 @@ class Permissions extends Component {
}
checkSemVer(version) {
let showAggregation = false;
let showUpsertSection = false;
try {
showAggregation = semverCheck('aggregationPerm', version);
if (showAggregation) {
this.setState({ ...this.state, showAggregation: true });
} else {
this.setState({ ...this.state, showAggregation: false });
}
showUpsertSection = !semverCheck('permHideUpsertSection', version);
this.setState({
...this.state,
showAggregation,
showUpsertSection,
});
} catch (e) {
console.error(e);
this.setState({ ...this.state, showAggregation: false });
this.setState({
...this.state,
showAggregation: false,
showUpsertSection: false,
});
}
return Promise.resolve();
}
@ -685,9 +691,12 @@ class Permissions extends Component {
</div>
);
};
const getUpsertSection = permsState => {
let _upsertSection = '';
if (!this.state.showUpsertSection) {
return null;
}
let _upsertSection;
const query = permsState.query;
if (query === 'insert') {
@ -730,7 +739,6 @@ class Permissions extends Component {
</div>
);
}
return _upsertSection;
};

View File

@ -12,6 +12,7 @@ const componentsSemver = {
schemaStitching: '1.0.0-alpha30',
webhookEnvSupport: '1.0.0-alpha29',
insertPermRestrictColumns: '1.0.0-alpha28',
permHideUpsertSection: '1.0.0-alpha32',
};
const getPreRelease = version => {

View File

@ -302,12 +302,15 @@ E.g.:
Conflict Clause
^^^^^^^^^^^^^^^
Conflict clause is used to convert an *insert* query to an *upsert* query. *Upsert* respects the table's *update*
permissions before editing an existing row in case of a conflict. Hence the conflict clause is permitted only if a
table has *update* permissions defined.
.. code-block:: none
on_conflict: {
constraint: <unique_constraint_name>!
[update_columns: [table_column!]]
[action: [update|ignore]]
constraint: table_constraint!
update_columns: [table_update_column!]!
}
E.g.:

View File

@ -13,6 +13,11 @@ the request.
Convert insert mutation to upsert
---------------------------------
.. note::
Only tables with **update** permissions are **upsertable**. i.e. a table's update permissions are respected
before updating an existing row in case of a conflict.
To convert an :doc:`insert mutation <insert>` into an upsert, you need to specify a unique or primary key constraint
and the columns to be updated in the case of a violation of that constraint using the ``on_conflict`` argument. You can
specify a constraint using the ``constraint`` field and choose the columns to update using the
@ -20,61 +25,11 @@ specify a constraint using the ``constraint`` field and choose the columns to up
upsert request in case of conflicts.
.. admonition:: Fetching Postgres constraint names
You can fetch the name of unique or primary key constraints by querying the ``information_schema.table_constraints`` table.
GraphQL Engine will automatically generate constraint names as enum values for ``constraint`` (try autocompleting in GraphiQL).
Typically, the constraint is automatically named as ``<table-name>_<column-name>_key``.
You can fetch the name of unique or primary key constraints by querying the ``information_schema.table_constraints``
table. GraphQL Engine will automatically generate constraint names as enum values for ``constraint`` (try
autocompleting in GraphiQL). Typically, the constraint is automatically named as ``<table-name>_<column-name>_key``.
Update all columns on conflict
------------------------------
When you don't explicitly specify ``update_columns``, all the columns that are present in any of the input objects are
updated for all objects. i.e. if a column value is not passed for an object but is passed for another object, its value
will be set to its default value.
**Example:** Upsert into ``user`` table using unique constraint ``user_name_key``:
.. graphiql::
:view_only:
:query:
mutation upsert_user {
insert_user(
objects: [
{ name: "John", email_sent: true, is_premium: true },
{ name: "Jack", email_sent: true }
]
on_conflict: { constraint: user_name_key }
){
affected_rows
returning{
name
email_sent
is_premium
}
}
}
:response:
{
"data": {
"insert_user": {
"affected_rows": 2,
"returning": [
{
"name": "John",
"email_sent": true,
"is_premium": true
},
{
"name": "Jack",
"email_sent": true,
"is_premium": false
}
]
}
}
}
Here ``is_premium`` value of "Jack" is reset to its default value as it is passed for "John" but not for "Jack".
Update selected columns on conflict
-----------------------------------
@ -116,11 +71,10 @@ specified in ``update_columns``:
}
}
Ignore request on conflict
--------------------------
If ``update_columns`` is an **empty array** then GraphQL Engine ignore changes on conflict. Insert a new object into the author
table or, if the unique constraint, ``author_name_key``, is violated, ignore the request
If ``update_columns`` is an **empty array** then GraphQL Engine ignore changes on conflict. Insert a new object into
the author table or, if the unique constraint, ``author_name_key``, is violated, ignore the request
.. graphiql::
:view_only:
@ -150,86 +104,6 @@ table or, if the unique constraint, ``author_name_key``, is violated, ignore the
In this case, the insert mutation is ignored because there is a conflict.
Using **action** argument
-------------------------
.. note::
The ``action`` argument is deprecated.
The ``update_columns`` argument will always take precedence over the ``action`` argument
On conflict, you can choose to either ignore the mutation (``action: ignore``) or update the row that caused the conflict
(``action: update``). ``ignore`` and ``update`` are enum values for ``action``.
For the following examples, assume there's a unique constraint on the ``name`` column of the ``author`` table.
With constraint name and update
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Insert a new object in the author table or, if the unique constraint, ``author_name_key``, is violated, update
the columns that are given in objects:
.. graphiql::
:view_only:
:query:
mutation upsert_author {
insert_author(
objects: [
{name: "John", id: 10}
],
on_conflict: {
constraint: author_name_key,
action: update
}
) {
affected_rows
}
}
:response:
{
"data": {
"insert_author": {
"affected_rows": 1
}
}
}
The response shown above assumes that the name of the author in our object is not unique and then
*updates* the corresponding row in the database.
With constraint name and ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Insert a new object into the author table or, if the unique constraint, ``author_name_key``, is violated,
ignore the request:
.. graphiql::
:view_only:
:query:
mutation upsert_author {
insert_author(
objects: [
{name: "John", id: 10}
],
on_conflict: {
constraint: author_name_key,
action: ignore
}
) {
affected_rows
}
}
:response:
{
"data": {
"insert_author": {
"affected_rows": 0
}
}
}
In this case, the insert mutation is ignored because there is a conflict.
Upsert in nested mutations
--------------------------
You can specify ``on_conflict`` clause while inserting nested objects
@ -276,5 +150,5 @@ You can specify ``on_conflict`` clause while inserting nested objects
Inserting nested objects fails when:
- Any of upsert in object relationships does not affect any rows (``update_columns: []`` or ``action: ignore``)
- Array relationships are queued for insert and parent insert does not affect any rows (``update_columns: []`` or ``action: ignore``)
- Any of upsert in object relationships does not affect any rows (``update_columns: []``)
- Array relationships are queued for insert and parent insert does not affect any rows (``update_columns: []``)

View File

@ -4,6 +4,7 @@ module Hasura.GraphQL.Resolve.Context
, OrdByCtx
, OrdByItemMap
, OrdByItem(..)
, UpdPermForIns
, InsCtx(..)
, InsCtxMap
, RespTx
@ -54,6 +55,7 @@ data InsResp
$(J.deriveJSON (J.aesonDrop 3 J.snakeCase) ''InsResp)
type RespTx = Q.TxE QErr BL.ByteString
type LazyRespTx = LazyTx QErr BL.ByteString
type PrepFn m = (PGColType, PGColValue) -> m S.SQLExp

View File

@ -29,12 +29,15 @@ type OrdByCtx = Map.HashMap G.NamedType OrdByItemMap
-- insert context
type RelationInfoMap = Map.HashMap RelName RelInfo
type UpdPermForIns = ([PGCol], AnnBoolExpSQL)
data InsCtx
= InsCtx
{ icView :: !QualifiedTable
, icColumns :: ![PGColInfo]
, icSet :: !InsSetCols
, icRelations :: !RelationInfoMap
, icUpdPerm :: !(Maybe UpdPermForIns)
} deriving (Show, Eq)
type InsCtxMap = Map.HashMap QualifiedTable InsCtx

View File

@ -2,9 +2,7 @@ module Hasura.GraphQL.Resolve.Insert
(convertInsert)
where
import Data.Foldable (foldrM)
import Data.Has
import Data.List (intersect, union)
import Hasura.Prelude
import Hasura.Server.Utils
@ -13,7 +11,6 @@ import qualified Data.Aeson.Casing as J
import qualified Data.Aeson.TH as J
import qualified Data.HashMap.Strict as Map
import qualified Data.HashMap.Strict.InsOrd as OMap
import qualified Data.HashSet as Set
import qualified Data.Sequence as Seq
import qualified Data.Text as T
import qualified Language.GraphQL.Draft.Syntax as G
@ -33,6 +30,7 @@ import Hasura.GraphQL.Resolve.Select
import Hasura.GraphQL.Validate.Field
import Hasura.GraphQL.Validate.Types
import Hasura.RQL.DML.Internal (dmlTxErrorHandler)
import Hasura.RQL.GBoolExp (toSQLBoolExp)
import Hasura.RQL.Types
import Hasura.SQL.Types
import Hasura.SQL.Value
@ -80,10 +78,6 @@ data AnnInsObj
, _aioArrRels :: ![ArrRelIns]
} deriving (Show, Eq)
getAllInsCols :: [AnnInsObj] -> [PGCol]
getAllInsCols =
Set.toList . Set.fromList . concatMap (map _1 . _aioColumns)
mkAnnInsObj
:: (MonadError QErr m, Has InsCtxMap r, MonadReader r m)
=> RelationInfoMap
@ -116,14 +110,14 @@ traverseInsObj rim (gName, annVal) (AnnInsObj cols objRels arrRels) =
relInfo <- onNothing (Map.lookup relName rim) $
throw500 $ "relation " <> relName <<> " not found"
(rtView, rtCols, rtDefVals, rtRelInfoMap) <- resolveInsCtx $ riRTable relInfo
let rTable = riRTable relInfo
InsCtx rtView rtCols rtDefVals rtRelInfoMap rtUpdPerm <- getInsCtx rTable
withPathK (G.unName gName) $ case riType relInfo of
ObjRel -> do
dataObj <- asObject dataVal
annDataObj <- mkAnnInsObj rtRelInfoMap dataObj
let insCols = getAllInsCols [annDataObj]
ccM <- forM onConflictM $ parseOnConflict insCols
ccM <- forM onConflictM $ parseOnConflict rTable rtUpdPerm
let singleObjIns = AnnIns annDataObj ccM rtView rtCols rtDefVals
objRelIns = RelIns singleObjIns relInfo
return (AnnInsObj cols (objRelIns:objRels) arrRels)
@ -133,35 +127,31 @@ traverseInsObj rim (gName, annVal) (AnnInsObj cols objRels arrRels) =
annDataObjs <- forM arrDataVals $ \arrDataVal -> do
dataObj <- asObject arrDataVal
mkAnnInsObj rtRelInfoMap dataObj
let insCols = getAllInsCols annDataObjs
ccM <- forM onConflictM $ parseOnConflict insCols
ccM <- forM onConflictM $ parseOnConflict rTable rtUpdPerm
let multiObjIns = AnnIns annDataObjs ccM rtView rtCols rtDefVals
arrRelIns = RelIns multiObjIns relInfo
return (AnnInsObj cols objRels (arrRelIns:arrRels))
parseOnConflict
:: (MonadError QErr m)
=> [PGCol] -> AnnGValue -> m RI.ConflictClauseP1
parseOnConflict inpCols val = withPathK "on_conflict" $
=> QualifiedTable -> Maybe UpdPermForIns
-> AnnGValue -> m RI.ConflictClauseP1
parseOnConflict tn updFiltrM val = withPathK "on_conflict" $
flip withObject val $ \_ obj -> do
actionM <- forM (OMap.lookup "action" obj) parseAction
constraint <- parseConstraint obj
updColsM <- forM (OMap.lookup "update_columns" obj) parseColumns
-- consider "action" if "update_columns" is not mentioned
return $ mkConflictClause $ case (updColsM, actionM) of
(Just [], _) -> RI.CCDoNothing $ Just constraint
(Just cols, _) -> RI.CCUpdate constraint cols
(Nothing, Just CAIgnore) -> RI.CCDoNothing $ Just constraint
(Nothing, _) -> RI.CCUpdate constraint inpCols
constraint <- RI.Constraint <$> parseConstraint obj
updCols <- getUpdCols obj
case updCols of
[] -> return $ RI.CP1DoNothing $ Just constraint
_ -> do
(_, updFiltr) <- onNothing updFiltrM $ throw500
"cannot update columns since update permission is not defined"
return $ RI.CP1Update constraint updCols $ toSQLBoolExp (S.mkQual tn) updFiltr
where
parseAction v = do
(enumTy, enumVal) <- asEnumVal v
case G.unName $ G.unEnumValue enumVal of
"ignore" -> return CAIgnore
"update" -> return CAUpdate
_ -> throw500 $
"only \"ignore\" and \"update\" allowed for enum type "
<> showNamedTy enumTy
getUpdCols o = do
updColsVal <- onNothing (OMap.lookup "update_columns" o) $ throw500
"\"update_columns\" argument in expected in \"on_conflict\" field "
parseColumns updColsVal
parseConstraint o = do
v <- onNothing (OMap.lookup "constraint" o) $ throw500
@ -169,11 +159,6 @@ parseOnConflict inpCols val = withPathK "on_conflict" $
(_, enumVal) <- asEnumVal v
return $ ConstraintName $ G.unName $ G.unEnumValue enumVal
mkConflictClause (RI.CCDoNothing constrM) =
RI.CP1DoNothing $ fmap RI.Constraint constrM
mkConflictClause (RI.CCUpdate constr updCols) =
RI.CP1Update (RI.Constraint constr) updCols
toSQLExps :: (MonadError QErr m, MonadState PrepArgs m)
=> [(PGCol, AnnGValue)] -> m [(PGCol, S.SQLExp)]
toSQLExps cols =
@ -272,7 +257,7 @@ validateInsert
validateInsert insCols objRels addCols = do
-- validate insertCols
unless (null insConflictCols) $ throwVE $
"cannot insert " <> pgColsToText insConflictCols
"cannot insert " <> showPGCols insConflictCols
<> " columns as their values are already being determined by parent insert"
forM_ objRels $ \relInfo -> do
@ -282,11 +267,10 @@ validateInsert insCols objRels addCols = do
lColConflicts = lCols `intersect` (addCols <> insCols)
withPathK relNameTxt $ unless (null lColConflicts) $ throwVE $
"cannot insert object relation ship " <> relName
<<> " as " <> pgColsToText lColConflicts
<<> " as " <> showPGCols lColConflicts
<> " column values are already determined"
where
insConflictCols = insCols `intersect` addCols
pgColsToText cols = T.intercalate ", " $ map getPGColTxt cols
-- | insert an object relationship and return affected rows
-- | and parent dependent columns
@ -463,12 +447,11 @@ convertInsert
-> Field -- the mutation field
-> Convert RespTx
convertInsert role tn fld = prefixErrPath fld $ do
(vn, tableCols, defValMap, relInfoMap) <- resolveInsCtx tn
InsCtx vn tableCols defValMap relInfoMap updPerm <- getInsCtx tn
annVals <- withArg arguments "objects" asArray
annObjs <- mapM asObject annVals
annInsObjs <- forM annObjs $ mkAnnInsObj relInfoMap
let insCols = getAllInsCols annInsObjs
conflictClauseM <- forM onConflictM $ parseOnConflict insCols
conflictClauseM <- forM onConflictM $ parseOnConflict tn updPerm
mutFlds <- convertMutResp (_fType fld) $ _fSelSet fld
let multiObjIns = AnnIns annInsObjs conflictClauseM vn tableCols defValMap
return $ prefixErrPath fld $ insertMultipleObjects role tn
@ -478,22 +461,16 @@ convertInsert role tn fld = prefixErrPath fld $ do
onConflictM = Map.lookup "on_conflict" arguments
-- helper functions
resolveInsCtx
getInsCtx
:: (MonadError QErr m, MonadReader r m, Has InsCtxMap r)
=> QualifiedTable
-> m ( QualifiedTable
, [PGColInfo]
, Map.HashMap PGCol S.SQLExp
, RelationInfoMap
)
resolveInsCtx tn = do
=> QualifiedTable -> m InsCtx
getInsCtx tn = do
ctxMap <- asks getter
InsCtx view colInfos setVals relInfoMap <-
onNothing (Map.lookup tn ctxMap) $
insCtx <- onNothing (Map.lookup tn ctxMap) $
throw500 $ "table " <> tn <<> " not found"
let defValMap = S.mkColDefValMap $ map pgiName colInfos
defValWithSet = Map.union setVals defValMap
return (view, colInfos, defValWithSet, relInfoMap)
let defValMap = S.mkColDefValMap $ map pgiName $ icColumns insCtx
setCols = icSet insCtx
return $ insCtx {icSet = Map.union setCols defValMap}
fetchVal :: (MonadError QErr m)
=> T.Text -> Map.HashMap T.Text a -> m a

View File

@ -32,6 +32,7 @@ import Hasura.GraphQL.Validate.Types
import Hasura.Prelude
import Hasura.RQL.DML.Internal (mkAdminRolePermInfo)
import Hasura.RQL.Types
import Hasura.Server.Utils
import Hasura.SQL.Types
@ -912,6 +913,10 @@ mkConstraintInpTy :: QualifiedTable -> G.NamedType
mkConstraintInpTy tn =
G.NamedType $ qualTableToName tn <> "_constraint"
-- conflict_action
conflictActionTy :: G.NamedType
conflictActionTy = G.NamedType "conflict_action"
-- table_update_column
mkUpdColumnInpTy :: QualifiedTable -> G.NamedType
mkUpdColumnInpTy tn =
@ -921,6 +926,7 @@ mkUpdColumnInpTy tn =
mkSelColumnInpTy :: QualifiedTable -> G.NamedType
mkSelColumnInpTy tn =
G.NamedType $ qualTableToName tn <> "_select_column"
{-
input table_obj_rel_insert_input {
data: table_insert_input!
@ -1009,21 +1015,16 @@ input table_on_conflict {
mkOnConflictInp :: QualifiedTable -> InpObjTyInfo
mkOnConflictInp tn =
mkHsraInpTyInfo (Just desc) (mkOnConflictInpTy tn) $ fromInpValL
[actionInpVal, constraintInpVal, updateColumnsInpVal]
[constraintInpVal, updateColumnsInpVal]
where
desc = G.Description $
"on conflict condition type for table " <>> tn
actionDesc = "action when conflict occurs (deprecated)"
actionInpVal = InpValInfo (Just actionDesc) (G.Name "action") $
G.toGT $ G.NamedType "conflict_action"
constraintInpVal = InpValInfo Nothing (G.Name "constraint") $
G.toGT $ G.toNT $ mkConstraintInpTy tn
updateColumnsInpVal = InpValInfo Nothing (G.Name "update_columns") $
G.toGT $ G.toLT $ G.toNT $ mkUpdColumnInpTy tn
G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkUpdColumnInpTy tn
{-
insert_table(
@ -1090,12 +1091,12 @@ mkSelColumnTy tn cols = enumTyInfo
desc = G.Description $
"select columns of table " <>> tn
mkConflictActionTy :: EnumTyInfo
mkConflictActionTy = mkHsraEnumTyInfo (Just desc) ty $ mapFromL _eviVal
[enumValIgnore, enumValUpdate]
mkConflictActionTy :: Bool -> EnumTyInfo
mkConflictActionTy updAllowed =
mkHsraEnumTyInfo (Just desc) conflictActionTy $ mapFromL _eviVal $
[enumValIgnore] <> bool [] [enumValUpdate] updAllowed
where
desc = G.Description "conflict action"
ty = G.NamedType "conflict_action"
enumValIgnore = EnumValInfo (Just "ignore the insert on this row")
(G.EnumValue "ignore") False
enumValUpdate = EnumValInfo (Just "update the row with the given values")
@ -1257,12 +1258,13 @@ mkOnConflictTypes
mkOnConflictTypes tn c cols =
bool [] tyInfos
where
tyInfos = [ TIEnum mkConflictActionTy
tyInfos = [ TIEnum $ mkConflictActionTy isUpdAllowed
, TIEnum $ mkConstriantTy tn constraints
, TIEnum $ mkUpdColumnTy tn cols
, TIInpObj $ mkOnConflictInp tn
]
constraints = filter isUniqueOrPrimary c
isUpdAllowed = not $ null cols
mkGCtxRole'
:: QualifiedTable
@ -1279,10 +1281,8 @@ mkGCtxRole'
-- constraints
-> [TableConstraint]
-> Maybe ViewInfo
-- all columns
-> [PGCol]
-> TyAgg
mkGCtxRole' tn insPermM selPermM updColsM delPermM pkeyCols constraints viM allCols =
mkGCtxRole' tn insPermM selPermM updColsM delPermM pkeyCols constraints viM =
TyAgg (mkTyInfoMap allTypes) fieldMap ordByCtx
where
@ -1290,7 +1290,8 @@ mkGCtxRole' tn insPermM selPermM updColsM delPermM pkeyCols constraints viM allC
ordByCtx = fromMaybe Map.empty ordByCtxM
upsertPerm = or $ fmap snd insPermM
isUpsertable = upsertable constraints upsertPerm $ isJust viM
onConflictTypes = mkOnConflictTypes tn constraints allCols isUpsertable
updatableCols = maybe [] (map pgiName) updColsM
onConflictTypes = mkOnConflictTypes tn constraints updatableCols isUpsertable
jsonOpTys = fromMaybe [] updJSONOpInpObjTysM
relInsInpObjTys = maybe [] (map TIInpObj) $
mutHelper viIsInsertable relInsInpObjsM
@ -1454,7 +1455,7 @@ getRootFldsRole' tn primCols constraints fields insM selM updM delM viM =
colInfos = fst $ validPartitionFieldInfoMap fields
getInsDet (hdrs, upsertPerm) =
let isUpsertable = upsertable constraints upsertPerm $ isJust viM
in ( OCInsert tn hdrs
in ( OCInsert tn $ hdrs `union` maybe [] _3 updM
, Right $ mkInsMutFld tn isUpsertable
)
@ -1520,8 +1521,8 @@ getSelPerm tableCache fields role selPermInfo = do
mkInsCtx
:: MonadError QErr m
=> RoleName
-> TableCache -> FieldInfoMap -> InsPermInfo -> m InsCtx
mkInsCtx role tableCache fields insPermInfo = do
-> TableCache -> FieldInfoMap -> InsPermInfo -> Maybe UpdPermInfo -> m InsCtx
mkInsCtx role tableCache fields insPermInfo updPermM = do
relTupsM <- forM rels $ \relInfo -> do
let remoteTable = riRTable relInfo
relName = riName relInfo
@ -1532,12 +1533,15 @@ mkInsCtx role tableCache fields insPermInfo = do
isInsertable insPermM viewInfoM
let relInfoMap = Map.fromList $ catMaybes relTupsM
return $ InsCtx iView cols setCols relInfoMap
return $ InsCtx iView cols setCols relInfoMap updPermForIns
where
cols = getValidCols fields
rels = getValidRels fields
iView = ipiView insPermInfo
setCols = ipiSet insPermInfo
mkUpdPermForIns upi =
(Set.toList $ upiCols upi, upiFilter upi)
updPermForIns = mkUpdPermForIns <$> updPermM
isInsertable Nothing _ = False
isInsertable (Just _) viewInfoM = isMutable viIsInsertable viewInfoM
@ -1554,7 +1558,10 @@ mkAdminInsCtx tn tc fields = do
return $ bool Nothing (Just (relName, relInfo)) $
isMutable viIsInsertable viewInfoM
return $ InsCtx tn cols Map.empty $ Map.fromList $ catMaybes relTupsM
let relInfoMap = Map.fromList $ catMaybes relTupsM
updPerm = (map pgiName cols, noFilter)
return $ InsCtx tn cols Map.empty relInfoMap $ Just updPerm
where
cols = getValidCols fields
rels = getValidRels fields
@ -1573,17 +1580,16 @@ mkGCtxRole
mkGCtxRole tableCache tn fields pCols constraints viM role permInfo = do
selPermM <- mapM (getSelPerm tableCache fields role) $ _permSel permInfo
tabInsCtxM <- forM (_permIns permInfo) $ \ipi -> do
tic <- mkInsCtx role tableCache fields ipi
return (tic, ipiAllowUpsert ipi)
tic <- mkInsCtx role tableCache fields ipi $ _permUpd permInfo
return (tic, isJust $ _permUpd permInfo)
let updColsM = filterColInfos . upiCols <$> _permUpd permInfo
tyAgg = mkGCtxRole' tn tabInsCtxM selPermM updColsM
(void $ _permDel permInfo) pColInfos constraints viM allCols
(void $ _permDel permInfo) pColInfos constraints viM
rootFlds = getRootFldsRole tn pCols constraints fields viM permInfo
insCtxMap = maybe Map.empty (Map.singleton tn) $ fmap fst tabInsCtxM
return (tyAgg, rootFlds, insCtxMap)
where
colInfos = getValidCols fields
allCols = map pgiName colInfos
pColInfos = getColInfos pCols colInfos
filterColInfos allowedSet =
filter ((`Set.member` allowedSet) . pgiName) colInfos
@ -1602,7 +1608,7 @@ getRootFldsRole tn pCols constraints fields viM (RolePermInfo insM selM updM del
(mkUpd <$> updM) (mkDel <$> delM)
viM
where
mkIns i = (ipiRequiredHeaders i, ipiAllowUpsert i)
mkIns i = (ipiRequiredHeaders i, isJust updM)
mkSel s = ( spiFilter s, spiLimit s
, spiRequiredHeaders s, spiAllowAgg s
)
@ -1623,7 +1629,7 @@ mkGCtxMapTable tableCache (TableInfo tn _ fields rolePerms constraints pkeyCols
adminInsCtx <- mkAdminInsCtx tn tableCache fields
let adminCtx = mkGCtxRole' tn (Just (adminInsCtx, True))
(Just (True, selFlds)) (Just colInfos) (Just ())
pkeyColInfos validConstraints viewInfo allCols
pkeyColInfos validConstraints viewInfo
adminInsCtxMap = Map.singleton tn adminInsCtx
return $ Map.insert adminRole (adminCtx, adminRootFlds, adminInsCtxMap) m
where

View File

@ -12,11 +12,11 @@ import Control.Monad.Reader as M
import Control.Monad.State as M
import Data.Bool as M (bool)
import Data.Either as M (lefts, partitionEithers, rights)
import Data.Foldable as M (toList)
import Data.Foldable as M (foldrM, toList)
import Data.Hashable as M (Hashable)
import Data.List as M (find, foldl', group, intercalate,
lookup, sort, sortBy, sortOn,
union)
intersect, lookup, sort, sortBy,
sortOn, union, unionBy, (\\))
import Data.Maybe as M (catMaybes, fromMaybe, isJust,
isNothing, listToMaybe, mapMaybe,
maybeToList)

View File

@ -48,9 +48,9 @@ module Hasura.RQL.DDL.Permission
import Hasura.Prelude
import Hasura.RQL.DDL.Permission.Internal
import Hasura.RQL.DDL.Permission.Triggers
import Hasura.RQL.DML.Internal (onlyPositiveInt)
import Hasura.RQL.Types
import Hasura.RQL.DML.Internal
import Hasura.RQL.GBoolExp
import Hasura.RQL.Types
import Hasura.SQL.Types
import qualified Database.PG.Query as Q
@ -59,7 +59,6 @@ import qualified Hasura.SQL.DML as S
import Data.Aeson
import Data.Aeson.Casing
import Data.Aeson.TH
import Data.List
import Language.Haskell.TH.Syntax (Lift)
import qualified Data.HashMap.Strict as HM
@ -69,10 +68,9 @@ import qualified Data.Text as T
-- Insert permission
data InsPerm
= InsPerm
{ icCheck :: !BoolExp
, icAllowUpsert :: !(Maybe Bool)
, icSet :: !(Maybe Object)
, icColumns :: !(Maybe PermColSpec)
{ icCheck :: !BoolExp
, icSet :: !(Maybe Object)
, icColumns :: !(Maybe PermColSpec)
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''InsPerm)
@ -108,14 +106,13 @@ buildInsPermInfo
=> TableInfo
-> PermDef InsPerm
-> m (WithDeps InsPermInfo)
buildInsPermInfo tabInfo (PermDef rn (InsPerm chk upsrt set mCols) _) = withPathK "permission" $ do
buildInsPermInfo tabInfo (PermDef rn (InsPerm chk set mCols) _) = withPathK "permission" $ do
(be, beDeps) <- withPathK "check" $
-- procBoolExp tn fieldInfoMap (S.QualVar "NEW") chk
procBoolExp tn fieldInfoMap chk
let deps = mkParentDep tn : beDeps
fltrHeaders = getDependentHeaders chk
setObj = fromMaybe mempty set
allowUpsrt = fromMaybe False upsrt
setColsSQL <- withPathK "set" $
fmap HM.fromList $ forM (HM.toList setObj) $ \(t, val) -> do
let pgCol = PGCol t
@ -125,24 +122,28 @@ buildInsPermInfo tabInfo (PermDef rn (InsPerm chk upsrt set mCols) _) = withPath
return (pgCol, sqlExp)
let setHdrs = mapMaybe (fetchHdr . snd) (HM.toList setObj)
reqHdrs = fltrHeaders `union` setHdrs
preSetCols = HM.union setColsSQL nonInsColVals
return (InsPermInfo vn be allowUpsrt preSetCols reqHdrs, deps)
preSetCols <- HM.union setColsSQL <$> nonInsColVals
return (InsPermInfo vn be preSetCols reqHdrs, deps)
where
fieldInfoMap = tiFieldInfoMap tabInfo
tn = tiName tabInfo
vn = buildViewName tn rn PTInsert
allCols = map pgiName $ getCols fieldInfoMap
nonInsCols = case mCols of
Nothing -> []
Just cols -> (\\) allCols $ convColSpec fieldInfoMap cols
nonInsColVals = S.mkColDefValMap nonInsCols
Nothing -> return []
Just cols -> do
let insCols = convColSpec fieldInfoMap cols
withPathK "columns" $
indexedForM_ insCols $ \c -> void $ askPGType fieldInfoMap c ""
return $ allCols \\ insCols
nonInsColVals = S.mkColDefValMap <$> nonInsCols
fetchHdr (String t) = bool Nothing (Just $ T.toLower t)
$ isUserVar t
fetchHdr _ = Nothing
buildInsInfra :: QualifiedTable -> InsPermInfo -> Q.TxE QErr ()
buildInsInfra tn (InsPermInfo vn be _ _ _) = do
buildInsInfra tn (InsPermInfo vn be _ _) = do
trigFnQ <- buildInsTrigFn vn tn $ toSQLBoolExp (S.QualVar "NEW") be
Q.catchE defaultTxErrorHandler $ do
-- Create the view

View File

@ -12,6 +12,7 @@ import qualified Data.Text.Lazy as LT
import Hasura.Prelude
import Hasura.RQL.DML.Internal
import Hasura.RQL.DML.Returning
import Hasura.RQL.GBoolExp
import Hasura.RQL.Instances ()
import Hasura.RQL.Types
import Hasura.SQL.Types
@ -26,7 +27,7 @@ data ConflictTarget
data ConflictClauseP1
= CP1DoNothing !(Maybe ConflictTarget)
| CP1Update !ConflictTarget ![PGCol]
| CP1Update !ConflictTarget ![PGCol] !S.BoolExp
deriving (Show, Eq)
data InsertQueryP1
@ -48,10 +49,10 @@ mkSQLInsert (InsertQueryP1 tn vn cols vals c mutFlds) =
toSQLConflict :: ConflictClauseP1 -> S.SQLConflict
toSQLConflict conflict = case conflict of
(CP1DoNothing Nothing) -> S.DoNothing Nothing
(CP1DoNothing (Just ct)) -> S.DoNothing $ Just $ toSQLCT ct
(CP1Update ct inpCols) -> S.Update (toSQLCT ct)
(S.buildSEWithExcluded inpCols)
CP1DoNothing Nothing -> S.DoNothing Nothing
CP1DoNothing (Just ct) -> S.DoNothing $ Just $ toSQLCT ct
CP1Update ct inpCols filtr -> S.Update (toSQLCT ct)
(S.buildSEWithExcluded inpCols) $ Just $ S.WhereFrag filtr
where
toSQLCT ct = case ct of
@ -100,6 +101,11 @@ convObj prepFn defInsVals setInsVals fieldInfoMap insObj = do
throw400 NotSupported $ "column " <> c <<> " is not insertable"
<> " for role " <>> role
validateInpCols :: (MonadError QErr m) => [PGCol] -> [PGCol] -> m ()
validateInpCols inpCols updColsPerm = forM_ inpCols $ \inpCol ->
unless (inpCol `elem` updColsPerm) $ throw400 ValidationFailed $
"column " <> inpCol <<> " is not updatable"
buildConflictClause
:: (UserInfoM m, QErrM m)
=> TableInfo
@ -119,14 +125,19 @@ buildConflictClause tableInfo inpCols (OnConflict mTCol mTCons act) =
"Expecting 'constraint' or 'constraint_on' when the 'action' is 'update'"
(Just col, Nothing, CAUpdate) -> do
validateCols col
return $ CP1Update (Column $ getPGCols col) inpCols
updFiltr <- getUpdFilter
return $ CP1Update (Column $ getPGCols col) inpCols $
toSQLBool updFiltr
(Nothing, Just cons, CAUpdate) -> do
validateConstraint cons
return $ CP1Update (Constraint cons) inpCols
updFiltr <- getUpdFilter
return $ CP1Update (Constraint cons) inpCols $
toSQLBool updFiltr
(Just _, Just _, _) -> throw400 UnexpectedPayload
"'constraint' and 'constraint_on' cannot be set at a time"
where
fieldInfoMap = tiFieldInfoMap tableInfo
toSQLBool = toSQLBoolExp (S.mkQual $ tiName tableInfo)
validateCols c = do
let targetcols = getPGCols c
@ -141,6 +152,13 @@ buildConflictClause tableInfo inpCols (OnConflict mTCol mTCons act) =
<<> " for table " <> tiName tableInfo
<<> " does not exist"
getUpdFilter = do
upi <- askUpdPermInfo tableInfo
let updFiltr = upiFilter upi
updCols = HS.toList $ upiCols upi
validateInpCols inpCols updCols
return updFiltr
convInsertQuery
:: (UserInfoM m, QErrM m, CacheRM m)
@ -189,8 +207,9 @@ convInsertQuery objsParser prepFn (InsertQuery tableName val oC mRetCols) = do
conflictClause <- withPathK "on_conflict" $ forM oC $ \c -> do
roleName <- askCurRole
unless (ipiAllowUpsert insPerm) $ throw400 PermissionDenied $
"upsert is not allowed for role" <>> roleName
unless (isTabUpdatable roleName tableInfo) $ throw400 PermissionDenied $
"upsert is not allowed for role " <> roleName
<<> " since update permissions are not defined"
buildConflictClause tableInfo inpCols c
return $ InsertQueryP1 tableName insView insCols sqlExps
@ -223,7 +242,7 @@ insertP2 (u, p) =
insertSQL = toSQL $ mkSQLInsert u
data ConflictCtx
= CCUpdate !ConstraintName ![PGCol]
= CCUpdate !ConstraintName ![PGCol] !S.BoolExp
| CCDoNothing !(Maybe ConstraintName)
deriving (Show, Eq)
@ -242,9 +261,9 @@ extractConflictCtx cp =
(CP1DoNothing mConflictTar) -> do
mConstraintName <- mapM extractConstraintName mConflictTar
return $ CCDoNothing mConstraintName
(CP1Update conflictTar inpCols) -> do
(CP1Update conflictTar inpCols filtr) -> do
constraintName <- extractConstraintName conflictTar
return $ CCUpdate constraintName inpCols
return $ CCUpdate constraintName inpCols filtr
where
extractConstraintName (Constraint cn) = return cn
extractConstraintName _ = throw400 NotSupported
@ -262,9 +281,10 @@ setConflictCtx conflictCtxM = do
conflictCtxToJSON (CCDoNothing constrM) =
encToText $ InsertTxConflictCtx CAIgnore constrM Nothing
conflictCtxToJSON (CCUpdate constr updCols) =
conflictCtxToJSON (CCUpdate constr updCols filtr) =
encToText $ InsertTxConflictCtx CAUpdate (Just constr) $
Just $ toSQLTxt $ S.buildSEWithExcluded updCols
Just $ toSQLTxt (S.buildSEWithExcluded updCols)
<> " " <> toSQLTxt (S.WhereFrag filtr)
runInsert
:: (QErrM m, UserInfoM m, CacheRWM m, MonadTx m)

View File

@ -47,7 +47,7 @@ mkAdminRolePermInfo ti =
. map fieldInfoToEither . M.elems $ tiFieldInfoMap ti
tn = tiName ti
i = InsPermInfo tn annBoolExpTrue True M.empty []
i = InsPermInfo tn annBoolExpTrue M.empty []
s = SelPermInfo (HS.fromList pgCols) tn annBoolExpTrue
Nothing True []
u = UpdPermInfo (HS.fromList pgCols) tn annBoolExpTrue []
@ -86,6 +86,13 @@ askPermInfo pa tableInfo = do
where
pt = permTypeToCode $ permAccToType pa
isTabUpdatable :: RoleName -> TableInfo -> Bool
isTabUpdatable role ti
| role == adminRole = True
| otherwise = isJust $ M.lookup role rpim >>= _permUpd
where
rpim = tiRolePermInfoMap ti
askInsPermInfo
:: (UserInfoM m, QErrM m)
=> TableInfo -> m InsPermInfo

View File

@ -9,7 +9,6 @@ module Hasura.RQL.DML.Select
where
import Data.Aeson.Types
import Data.List (unionBy)
import Instances.TH.Lift ()
import qualified Data.HashMap.Strict as HM

View File

@ -265,7 +265,6 @@ data InsPermInfo
= InsPermInfo
{ ipiView :: !QualifiedTable
, ipiCheck :: !AnnBoolExpSQL
, ipiAllowUpsert :: !Bool
, ipiSet :: !InsSetCols
, ipiRequiredHeaders :: ![T.Text]
} deriving (Show, Eq)

View File

@ -658,7 +658,7 @@ instance ToSQL SQLConflictTarget where
data SQLConflict
= DoNothing !(Maybe SQLConflictTarget)
| Update !SQLConflictTarget !SetExp
| Update !SQLConflictTarget !SetExp !(Maybe WhereFrag)
deriving (Show, Eq)
instance ToSQL SQLConflict where
@ -666,9 +666,9 @@ instance ToSQL SQLConflict where
toSQL (DoNothing (Just ct)) = "ON CONFLICT"
<-> toSQL ct
<-> "DO NOTHING"
toSQL (Update ct ex) = "ON CONFLICT"
toSQL (Update ct set whr) = "ON CONFLICT"
<-> toSQL ct <-> "DO UPDATE"
<-> toSQL ex
<-> toSQL set <-> toSQL whr
data SQLInsert = SQLInsert
{ siTable :: !QualifiedTable

View File

@ -206,6 +206,10 @@ instance ToSQL PGCol where
instance DQuote PGCol where
dquoteTxt (PGCol t) = t
showPGCols :: (Foldable t) => t PGCol -> T.Text
showPGCols cols =
T.intercalate ", " $ map (T.dquote . getPGColTxt) $ toList cols
data PGColType
= PGSmallInt
| PGInteger

View File

@ -3,7 +3,6 @@ module Hasura.Server.Utils where
import qualified Database.PG.Query.Connection as Q
import Data.Aeson
import Data.List (group, sort)
import Data.List.Split
import Network.URI
import System.Exit

View File

@ -21,6 +21,14 @@ query:
permission:
check:
author_id: X-Hasura-User-Id
- type: create_update_permission
args:
table: article
role: user
permission:
columns: '*'
filter:
author_id: X-Hasura-User-Id
- type: create_select_permission
args:
table: author
@ -36,4 +44,11 @@ query:
permission:
check:
id: X-HASURA-USER-ID
allow_upsert: true
- type: create_update_permission
args:
table: author
role: user
permission:
columns: '*'
filter:
id: X-Hasura-User-Id

View File

@ -2,11 +2,20 @@ description: Create a insert permission on test_table for user role
url: /v1/query
status: 200
query:
type: create_insert_permission
type: bulk
args:
table: test_table
role: user
permission:
check:
id: X-Hasura-User-Id
allow_upsert: true
- type: create_insert_permission
args:
table: test_table
role: user
permission:
check:
id: X-Hasura-User-Id
- type: create_update_permission
args:
table: test_table
role: user
permission:
filter:
id: X-Hasura-User-Id
columns: '*'

View File

@ -1,9 +1,9 @@
description: Upserts article data via GraphQL mutation as User role
url: /v1alpha1/graphql
status: 200
header:
headers:
X-Hasura-Role: user
X-Hasura-User-Id: 1
X-Hasura-User-Id: '1'
query:
query: |
mutation insert_article {
@ -12,6 +12,7 @@ query:
{
content: "Updated Article 1 content",
id: 1
author_id: 1
}
],
on_conflict: {
@ -21,6 +22,7 @@ query:
returning {
title
content
author_id
}
}
}

View File

@ -105,5 +105,4 @@ args:
permission:
check:
id: X-HASURA-USER-ID
allow_upsert: true

View File

@ -48,4 +48,4 @@ response:
- extensions:
code: validation-failed
path: $.selectionSet.insert_article.args.objects[0].author
message: "cannot insert object relation ship \"author\" as author_id column values are already determined"
message: "cannot insert object relation ship \"author\" as \"author_id\" column values are already determined"

View File

@ -6,7 +6,7 @@ response:
- extensions:
code: validation-failed
path: $.selectionSet.insert_author.args.objects[0].articles.data[0]
message: cannot insert author_id columns as their values are already being determined by parent insert
message: "cannot insert \"author_id\" columns as their values are already being determined by parent insert"
query:
query: |
mutation nested_author_insert {

View File

@ -22,7 +22,7 @@ query:
],
on_conflict: {
constraint: article_pkey,
action: ignore
update_columns: []
}
) {
returning {

View File

@ -1,35 +0,0 @@
description: Upserts article data via GraphQL mutation with only constraint
url: /v1alpha1/graphql
response:
data:
insert_article:
returning:
- content: Updated Article 1 content
title: Article 1
- content: Updated Article 2 content
title: Article 2
status: 200
query:
query: |
mutation insert_article {
insert_article (
objects: [
{
content: "Updated Article 1 content",
id: 1
},
{
content: "Updated Article 2 content",
id: 2
}
],
on_conflict: {
constraint: article_pkey
}
) {
returning {
title
content
}
}
}

View File

@ -25,7 +25,7 @@ query:
],
on_conflict: {
constraint: article_pkey,
action: update
update_columns: [content]
}
) {
returning {

View File

@ -1,26 +1,38 @@
description: Upserts article data via GraphQL mutation as User role
url: /v1alpha1/graphql
status: 200
header:
headers:
X-Hasura-Role: user
X-Hasura-User-Id: 1
X-Hasura-User-Id: '1'
response:
data:
insert_article:
affected_rows: 1
returning:
- title: Article 1
content: Updated Article 1 content
author_id: 1
query:
query: |
mutation insert_article {
insert_article (
objects: [
{
content: "Updated Article 1 content",
id: 1
content: "Updated Article 1 content"
author_id: 1
}
],
on_conflict: {
constraint: article_pkey
update_columns: [content]
}
) {
affected_rows
returning {
title
content
author_id
}
}
}

View File

@ -21,7 +21,7 @@
],
on_conflict: {
constraint: author_pkey,
action: ignore
update_columns: []
}
) {
affected_rows

View File

@ -4,11 +4,8 @@ status: 200
response:
data:
insert_author:
affected_rows: 1
returning:
- id: 1
name: Author 1
is_registered: false
affected_rows: 0
returning: []
headers:
X-Hasura-Role: user
X-Hasura-User-Id: '1'
@ -25,7 +22,7 @@ query:
],
on_conflict: {
constraint: author_pkey,
action: update
update_columns: []
}
) {
affected_rows

View File

@ -1,8 +1,8 @@
description: Insert Company data as user role
url: /v1alpha1/graphql
status: 200
header:
X-Hasura-Company-Id: 1
headers:
X-Hasura-Company-Id: '1'
X-Hasura-Role: user
response:
data:

View File

@ -1,7 +1,7 @@
description: Upsert Company name as user role
url: /v1alpha1/graphql
status: 200
header:
headers:
X-Hasura-Company-Id: '1'
X-Hasura-Role: user
response:

View File

@ -0,0 +1,37 @@
description: Resident 5 trying to update Resident 6 age
url: /v1alpha1/graphql
status: 200
headers:
X-Hasura-Role: resident
X-Hasura-Resident-Id: '5'
# affects zero rows
response:
data:
insert_resident:
affected_rows: 0
returning: []
query:
query: |
mutation {
insert_resident(
objects: [
{
id: 5
name: "Resident 6",
age: 23
}
]
on_conflict: {
constraint: resident_name_key
update_columns: [name, age]
}
){
affected_rows
returning{
id
name
age
is_user
}
}
}

View File

@ -39,7 +39,7 @@ args:
sql: |
CREATE TABLE resident (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
name TEXT NOT NULL UNIQUE,
age INTEGER NOT NULL,
is_user BOOLEAN DEFAULT FALSE NOT NULL
)
@ -116,6 +116,16 @@ args:
check:
author_id: X-Hasura-User-Id
#Article udpate permission for user
- type: create_update_permission
args:
table: article
role: user
permission:
filter:
author_id: X-Hasura-User-Id
columns: '*'
#Author select permission for user
- type: create_select_permission
args:
@ -129,7 +139,7 @@ args:
filter:
id: X-HASURA-USER-ID
#Author insert permission for user
#Author insert and update permission for user
#Only admin can set is_registered to true
- type: create_insert_permission
args:
@ -140,7 +150,17 @@ args:
$and:
- id: X-HASURA-USER-ID
- is_registered: false
allow_upsert: true
- type: create_update_permission
args:
table: author
role: user
permission:
columns: '*'
filter:
$and:
- id: X-HASURA-USER-ID
- is_registered: false
#Author insert permission for student
#A Student should specify their Bio
@ -152,7 +172,6 @@ args:
check:
bio:
_is_null: false
allow_upsert: true
#Insert Author table data
- type: insert
@ -169,10 +188,13 @@ args:
objects:
- content: Sample article content
title: Article 1
author_id: 1
- content: Sample article content
title: Article 2
author_id: 1
- content: Sample article content
title: Article 3
author_id: 2
#Company insert permission for user
- type: create_insert_permission
@ -182,7 +204,16 @@ args:
permission:
check:
id: X-HASURA-COMPANY-ID
allow_upsert: true
#Company update permission for user
- type: create_update_permission
args:
table: Company
role: user
permission:
filter:
id: X-HASURA-COMPANY-ID
columns: '*'
#Company select permission for user
- type: create_select_permission
@ -204,7 +235,6 @@ args:
permission:
check:
id: X-Hasura-Resident-Id
allow_upsert: true
set:
name: X-Hasura-Resident-Name
is_user: true
@ -231,7 +261,6 @@ args:
permission:
check:
id: X-Hasura-Infant-Id
allow_upsert: false
set:
name: X-Hasura-Infant-Name
id: X-Hasura-Infant-Id
@ -252,3 +281,40 @@ args:
filter:
id: X-Hasura-Infant-Id
#Create permissions for resident role on resident table
- type: create_insert_permission
args:
table: resident
role: resident
permission:
check:
id: X-Hasura-Resident-Id
- type: create_update_permission
args:
table: resident
role: resident
permission:
columns: '*'
filter:
id: X-Hasura-Resident-Id
- type: create_select_permission
args:
table: resident
role: resident
permission:
columns: '*'
filter:
id: X-Hasura-Resident-Id
#Insert residents
- type: insert
args:
table: resident
objects:
- id: 5
name: Resident 5
age: 21
- id: 6
name: Resident 6
age: 22

View File

@ -1,32 +0,0 @@
description: Upserts article data via GraphQL mutation with only constraint
url: /v1alpha1/graphql
headers:
X-Hasura-Role: user
X-Hasura-User-Id: '1'
status: 400
query:
query: |
mutation insert_article {
insert_article (
objects: [
{
content: "Updated Article 1 content",
id: 1,
author_id: 1
},
{
content: "Updated Article 2 content",
id: 2,
author_id: 1
}
],
on_conflict: {
constraint: article_pkey
}
) {
returning {
title
content
}
}
}

View File

@ -130,7 +130,6 @@ args:
permission:
check:
id: X-HASURA-USER-ID
allow_upsert: true
#Insert Author values
- type: insert

View File

@ -130,7 +130,6 @@ args:
permission:
check:
id: X-HASURA-USER-ID
allow_upsert: true
#Insert Author values
- type: insert

View File

@ -1,9 +1,9 @@
description: Upserts article data via GraphQL mutation as User role
url: /v1/query
status: 200
header:
headers:
X-Hasura-Role: user
X-Hasura-User-Id: 1
X-Hasura-User-Id: '1'
response:
affected_rows: 1
returning:
@ -16,9 +16,9 @@ query:
objects:
- content: Updated Article 1 content
id: 1
author_id: 1
on_conflict:
constraint_on:
- id
constraint: article_pkey
action: update
returning:
- content

View File

@ -0,0 +1,26 @@
description: Resident 1 trying to update Resident 2 age
url: /v1/query
status: 200
headers:
X-Hasura-Role: resident
X-Hasura-Resident-Id: '1'
# affects zero rows
response:
returning: []
affected_rows: 0
query:
type: insert
args:
table: resident
objects:
- id: 1
name: Resident 2
age: 23
on_conflict:
constraint: resident_name_key
action: update
returning:
- id
- name
- age

View File

@ -39,7 +39,7 @@ args:
sql: |
CREATE TABLE resident (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
name TEXT NOT NULL UNIQUE,
age INTEGER NOT NULL
)
- type: track_table
@ -93,7 +93,7 @@ args:
- author_id: X-HASURA-USER-ID
- is_published: true
#Article insert permission for user
#Article insert and udpate permission for user
- type: create_insert_permission
args:
table: article
@ -102,6 +102,15 @@ args:
check:
author_id: X-Hasura-User-Id
- type: create_update_permission
args:
table: article
role: user
permission:
columns: '*'
filter:
author_id: X-Hasura-User-Id
#Author select permission for user
- type: create_select_permission
args:
@ -115,7 +124,7 @@ args:
filter:
id: X-HASURA-USER-ID
#Author insert permission for user
#Author insert and update permission for user
#Only admin can set is_registered to true
- type: create_insert_permission
args:
@ -126,7 +135,17 @@ args:
$and:
- id: X-HASURA-USER-ID
- is_registered: false
allow_upsert: true
- type: create_update_permission
args:
table: author
role: user
permission:
columns: '*'
filter:
$and:
- id: X-HASURA-USER-ID
- is_registered: false
#Author insert permission for student
#A Student should specify their Bio
@ -138,7 +157,44 @@ args:
check:
bio:
_is_null: false
allow_upsert: true
#resident table permissions for resident role
- type: create_insert_permission
args:
table: resident
role: resident
permission:
check:
id: X-Hasura-Resident-Id
- type: create_update_permission
args:
table: resident
role: resident
permission:
columns: '*'
filter:
id: X-Hasura-Resident-Id
- type: create_select_permission
args:
table: resident
role: resident
permission:
columns: '*'
filter:
id: X-Hasura-Resident-Id
#Insert residents
- type: insert
args:
table: resident
objects:
- id: 1
name: Resident 1
age: 21
- id: 2
name: Resident 2
age: 22
#Insert Author table data
- type: insert
@ -155,7 +211,10 @@ args:
objects:
- content: Sample article content
title: Article 1
author_id: 1
- content: Sample article content
title: Article 2
author_id: 1
- content: Sample article content
title: Article 3
author_id: 2

View File

@ -130,7 +130,6 @@ args:
permission:
check:
id: X-HASURA-USER-ID
allow_upsert: true
#Insert Author values
- type: insert

View File

@ -49,9 +49,6 @@ class TestGraphqlInsertOnConflict(DefaultTestQueries):
def test_on_conflict_update(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_update.yaml")
def test_on_conflict_no_action_specified(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_no_action_specified.yaml")
def test_on_conflict_ignore(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/article_on_conflict_ignore_constraint.yaml")
hge_ctx.may_skip_test_teardown = True
@ -86,9 +83,6 @@ class TestGraphqlInsertPermission(DefaultTestQueries):
check_query_f(hge_ctx, self.dir() + "/author_on_conflict_ignore_user_role.yaml")
hge_ctx.may_skip_test_teardown = True
def test_user_on_conflict_err_no_action_specified(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/user_article_on_conflict_err_no_action_specified.yaml")
def test_user_err_missing_article_constraint(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/user_article_on_conflict_error_missing_article_constraint.yaml")
@ -131,6 +125,9 @@ class TestGraphqlInsertPermission(DefaultTestQueries):
def test_resident_infant_role_insert_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/resident_infant_fail.yaml")
def test_resident_5_modifies_resident_6_upsert(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/resident_5_modifies_resident_6_upsert.yaml")
@classmethod
def dir(cls):
return "queries/graphql_mutation/insert/permissions"

View File

@ -244,6 +244,9 @@ class TestV1InsertPermissions(DefaultTestQueries):
def test_student_role_insert_check_bio_fail(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/author_student_role_insert_check_bio_fail.yaml")
def test_resident_1_modifies_resident_2_upsert(self, hge_ctx):
check_query_f(hge_ctx, self.dir() + "/resident_1_modifies_resident_2_upsert.yaml")
@classmethod
def dir(cls):
return "queries/v1/insert/permissions"

View File

@ -130,7 +130,15 @@ args:
permission:
check:
id: X-HASURA-USER-ID
allow_upsert: true
- type: create_update_permission
args:
table: author
role: user
permission:
columns: '*'
filter:
id: X-HASURA-USER-ID
#Insert Author values
- type: insert