mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
apply update permissions for upsert mutations (#628)
This commit is contained in:
parent
58cfbed22e
commit
3026c49087
@ -14,7 +14,6 @@
|
||||
insert_permissions:
|
||||
- comment: null
|
||||
permission:
|
||||
allow_upsert: true
|
||||
check:
|
||||
user_id:
|
||||
_eq: X-HASURA-USER-ID
|
||||
|
@ -14,7 +14,6 @@
|
||||
insert_permissions:
|
||||
- comment: null
|
||||
permission:
|
||||
allow_upsert: true
|
||||
check:
|
||||
user_id:
|
||||
_eq: X-HASURA-USER-ID
|
||||
|
@ -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) => {
|
||||
|
@ -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
|
||||
);
|
||||
});
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -61,6 +61,7 @@ const getQueriesWithPermColumns = insert => {
|
||||
}
|
||||
return queries;
|
||||
};
|
||||
|
||||
const permChangeTypes = {
|
||||
save: 'update',
|
||||
delete: 'delete',
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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 => {
|
||||
|
@ -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.:
|
||||
|
@ -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: []``)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -265,7 +265,6 @@ data InsPermInfo
|
||||
= InsPermInfo
|
||||
{ ipiView :: !QualifiedTable
|
||||
, ipiCheck :: !AnnBoolExpSQL
|
||||
, ipiAllowUpsert :: !Bool
|
||||
, ipiSet :: !InsSetCols
|
||||
, ipiRequiredHeaders :: ![T.Text]
|
||||
} deriving (Show, Eq)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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: '*'
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,5 +105,4 @@ args:
|
||||
permission:
|
||||
check:
|
||||
id: X-HASURA-USER-ID
|
||||
allow_upsert: true
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -22,7 +22,7 @@ query:
|
||||
],
|
||||
on_conflict: {
|
||||
constraint: article_pkey,
|
||||
action: ignore
|
||||
update_columns: []
|
||||
}
|
||||
) {
|
||||
returning {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ query:
|
||||
],
|
||||
on_conflict: {
|
||||
constraint: article_pkey,
|
||||
action: update
|
||||
update_columns: [content]
|
||||
}
|
||||
) {
|
||||
returning {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
],
|
||||
on_conflict: {
|
||||
constraint: author_pkey,
|
||||
action: ignore
|
||||
update_columns: []
|
||||
}
|
||||
) {
|
||||
affected_rows
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -130,7 +130,6 @@ args:
|
||||
permission:
|
||||
check:
|
||||
id: X-HASURA-USER-ID
|
||||
allow_upsert: true
|
||||
|
||||
#Insert Author values
|
||||
- type: insert
|
||||
|
@ -130,7 +130,6 @@ args:
|
||||
permission:
|
||||
check:
|
||||
id: X-HASURA-USER-ID
|
||||
allow_upsert: true
|
||||
|
||||
#Insert Author values
|
||||
- type: insert
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -130,7 +130,6 @@ args:
|
||||
permission:
|
||||
check:
|
||||
id: X-HASURA-USER-ID
|
||||
allow_upsert: true
|
||||
|
||||
#Insert Author values
|
||||
- type: insert
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user