mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-20 06:58:39 +03:00
server: support relationship column comparison with root table columns
GitOrigin-RevId: 9d9bcb82a997c1f060df5ba0726c29d934e70d71
This commit is contained in:
parent
7638d1832a
commit
58cf521c62
11
CHANGELOG.md
11
CHANGELOG.md
@ -3,6 +3,17 @@
|
||||
## Next release
|
||||
(Add entries here in the order of: server, console, cli, docs, others)
|
||||
|
||||
### Support comparing columns across related tables in permission's boolean expressions
|
||||
|
||||
We now support comparing columns across related tables. For example:
|
||||
|
||||
Consider two tables, `items(id, name, quantity)` and `shopping_cart(id, item_id, quantity)`
|
||||
and these two tables are related via the `item_id` column. Now, while defining insert permission
|
||||
on the `shopping_cart` table, there can be a check to insert an item into the shopping cart
|
||||
only when there are enough present in the items inventory.
|
||||
|
||||
### Bug fixes and improvements
|
||||
|
||||
- console: add bigquery support (#1000)
|
||||
- cli: add support for bigquery in metadata operations
|
||||
|
||||
|
@ -793,6 +793,48 @@ Operator
|
||||
|
||||
**Operators for comparing columns (all column types except json, jsonb):**
|
||||
|
||||
**Column Comparison Operator**
|
||||
|
||||
.. parsed-literal::
|
||||
:class: haskell-pre
|
||||
|
||||
{
|
||||
PGColumn_: {
|
||||
Operator_: {
|
||||
PGColumn_ | ["$", PGColumn_]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column comparison operators can be used to compare columns of the same
|
||||
table or a related table. To compare a column of a table with another column of :
|
||||
|
||||
1. The same table -
|
||||
|
||||
.. parsed-literal::
|
||||
:class: haskell-pre
|
||||
|
||||
{
|
||||
PGColumn_: {
|
||||
Operator_: {
|
||||
PGColumn_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2. The table on which the permission is being defined on -
|
||||
|
||||
.. parsed-literal::
|
||||
:class: haskell-pre
|
||||
|
||||
{
|
||||
PGColumn_: {
|
||||
Operator_: {
|
||||
[$, PGColumn_]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
|
@ -2,8 +2,8 @@ module Hasura.Backends.BigQuery.DDL.BoolExp where
|
||||
|
||||
import Hasura.Prelude
|
||||
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.HashMap.Strict as Map
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.HashMap.Strict as Map
|
||||
|
||||
import Hasura.Backends.BigQuery.Instances.Types ()
|
||||
import Hasura.Backends.BigQuery.Types
|
||||
@ -18,11 +18,12 @@ parseBoolExpOperations
|
||||
:: forall m v
|
||||
. (MonadError QErr m)
|
||||
=> ValueParser 'BigQuery m v
|
||||
-> TableName
|
||||
-> FieldInfoMap (FieldInfo 'BigQuery)
|
||||
-> ColumnInfo 'BigQuery
|
||||
-> J.Value
|
||||
-> m [OpExpG 'BigQuery v]
|
||||
parseBoolExpOperations rhsParser _fields columnInfo value =
|
||||
parseBoolExpOperations rhsParser _table _fields columnInfo value =
|
||||
withPathK (columnName $ pgiColumn columnInfo) $
|
||||
parseOperations (pgiType columnInfo) value
|
||||
where
|
||||
|
@ -21,11 +21,12 @@ parseBoolExpOperations
|
||||
:: forall m v
|
||||
. (MonadError QErr m) -- , TableCoreInfoRM 'MSSQL m)
|
||||
=> ValueParser 'MSSQL m v
|
||||
-> TableName
|
||||
-> FieldInfoMap (FieldInfo 'MSSQL)
|
||||
-> ColumnInfo 'MSSQL
|
||||
-> J.Value
|
||||
-> m [OpExpG 'MSSQL v]
|
||||
parseBoolExpOperations rhsParser _fields columnInfo value =
|
||||
parseBoolExpOperations rhsParser _table _fields columnInfo value =
|
||||
withPathK (columnNameText $ pgiColumn columnInfo) $
|
||||
parseOperations (pgiType columnInfo) value
|
||||
where
|
||||
|
@ -39,13 +39,16 @@ instance ToTxt (ColumnReference 'Postgres) where
|
||||
|
||||
parseBoolExpOperations
|
||||
:: forall m v
|
||||
. (MonadError QErr m)
|
||||
. ( MonadError QErr m
|
||||
, TableCoreInfoRM 'Postgres m
|
||||
)
|
||||
=> ValueParser 'Postgres m v
|
||||
-> QualifiedTable
|
||||
-> FieldInfoMap (FieldInfo 'Postgres)
|
||||
-> ColumnInfo 'Postgres
|
||||
-> Value
|
||||
-> m [OpExpG 'Postgres v]
|
||||
parseBoolExpOperations rhsParser fim columnInfo value = do
|
||||
parseBoolExpOperations rhsParser rootTable fim columnInfo value = do
|
||||
restrictJSONColumn
|
||||
withPathK (getPGColTxt $ pgiColumn columnInfo) $
|
||||
parseOperations (ColumnReferenceColumn columnInfo) value
|
||||
@ -211,12 +214,12 @@ parseBoolExpOperations rhsParser fim columnInfo value = do
|
||||
parseGte = AGTE <$> parseOne -- >=
|
||||
parseLte = ALTE <$> parseOne -- <=
|
||||
|
||||
parseCeq = CEQ <$> decodeAndValidateRhsCol
|
||||
parseCne = CNE <$> decodeAndValidateRhsCol
|
||||
parseCgt = CGT <$> decodeAndValidateRhsCol
|
||||
parseClt = CLT <$> decodeAndValidateRhsCol
|
||||
parseCgte = CGTE <$> decodeAndValidateRhsCol
|
||||
parseClte = CLTE <$> decodeAndValidateRhsCol
|
||||
parseCeq = CEQ <$> decodeAndValidateRhsCol val
|
||||
parseCne = CNE <$> decodeAndValidateRhsCol val
|
||||
parseCgt = CGT <$> decodeAndValidateRhsCol val
|
||||
parseClt = CLT <$> decodeAndValidateRhsCol val
|
||||
parseCgte = CGTE <$> decodeAndValidateRhsCol val
|
||||
parseClte = CLTE <$> decodeAndValidateRhsCol val
|
||||
|
||||
parseLike = guardType stringTypes >> ALIKE <$> parseOne
|
||||
parseNlike = guardType stringTypes >> ANLIKE <$> parseOne
|
||||
@ -267,6 +270,33 @@ parseBoolExpOperations rhsParser fim columnInfo value = do
|
||||
return $ ASTDWithinGeog $ DWithinGeogOp dist from useSpheroid
|
||||
_ -> throwError $ buildMsg colTy [PGGeometry, PGGeography]
|
||||
|
||||
decodeAndValidateRhsCol :: Value -> m (PGCol, Maybe QualifiedTable)
|
||||
decodeAndValidateRhsCol v@(String _) = do
|
||||
j <- decodeValue v
|
||||
col <- validateRhsCol fim j
|
||||
pure (col, Nothing)
|
||||
decodeAndValidateRhsCol (Array path) = do
|
||||
case toList path of
|
||||
[] -> throw400 Unexpected "path cannot be empty"
|
||||
[col] -> do
|
||||
j <- decodeValue col
|
||||
col' <- validateRhsCol fim j
|
||||
pure $ (col', Nothing)
|
||||
(root : col : []) -> do
|
||||
root' :: Text <- decodeValue root
|
||||
unless (root' == "$") $
|
||||
throw400 NotSupported "Relationship references are not supported in column comparison RHS"
|
||||
rootTableInfo <-
|
||||
lookupTableCoreInfo rootTable
|
||||
>>= (`onNothing` (throw500 $ "unexpected: " <> rootTable <<> " doesn't exist"))
|
||||
j <- decodeValue col
|
||||
col' <- validateRhsCol (_tciFieldInfoMap rootTableInfo) j
|
||||
pure $ (col', Just rootTable)
|
||||
_ -> throw400 NotSupported "Relationship references are not supported in column comparison RHS"
|
||||
|
||||
decodeAndValidateRhsCol _ =
|
||||
throw400 Unexpected "a boolean expression JSON can either be a string or an array"
|
||||
|
||||
parseST3DDWithinObj = ABackendSpecific <$> do
|
||||
guardType [PGGeometry]
|
||||
DWithinGeomOp distVal fromVal <- parseVal
|
||||
@ -274,12 +304,9 @@ parseBoolExpOperations rhsParser fim columnInfo value = do
|
||||
from <- withPathK "from" $ parseOneNoSess colTy fromVal
|
||||
return $ AST3DDWithinGeom $ DWithinGeomOp dist from
|
||||
|
||||
decodeAndValidateRhsCol =
|
||||
parseVal >>= validateRhsCol
|
||||
|
||||
validateRhsCol rhsCol = do
|
||||
validateRhsCol fieldInfoMap rhsCol = do
|
||||
let errMsg = "column operators can only compare postgres columns"
|
||||
rhsType <- askColumnType fim rhsCol errMsg
|
||||
rhsType <- askColumnType fieldInfoMap rhsCol errMsg
|
||||
if colTy /= rhsType
|
||||
then throw400 UnexpectedPayload $
|
||||
"incompatible column types : " <> column <<> ", " <>> rhsCol
|
||||
|
@ -17,7 +17,6 @@ import Hasura.Backends.Postgres.Types.BoolExp
|
||||
import Hasura.RQL.Types
|
||||
import Hasura.SQL.Types
|
||||
|
||||
|
||||
-- This convoluted expression instead of col = val
|
||||
-- to handle the case of col : null
|
||||
equalsBoolExpBuilder :: SQLExpression 'Postgres -> SQLExpression 'Postgres -> S.BoolExp
|
||||
@ -37,38 +36,40 @@ notEqualsBoolExpBuilder qualColExp rhsExp =
|
||||
annBoolExp
|
||||
:: (QErrM m, TableCoreInfoRM b m, BackendMetadata b)
|
||||
=> ValueParser b m v
|
||||
-> TableName b
|
||||
-> FieldInfoMap (FieldInfo b)
|
||||
-> GBoolExp b ColExp
|
||||
-> m (AnnBoolExp b v)
|
||||
annBoolExp rhsParser fim boolExp =
|
||||
annBoolExp rhsParser rootTable fim boolExp =
|
||||
case boolExp of
|
||||
BoolAnd exps -> BoolAnd <$> procExps exps
|
||||
BoolOr exps -> BoolOr <$> procExps exps
|
||||
BoolNot e -> BoolNot <$> annBoolExp rhsParser fim e
|
||||
BoolNot e -> BoolNot <$> annBoolExp rhsParser rootTable fim e
|
||||
BoolExists (GExists refqt whereExp) ->
|
||||
withPathK "_exists" $ do
|
||||
refFields <- withPathK "_table" $ askFieldInfoMapSource refqt
|
||||
annWhereExp <- withPathK "_where" $
|
||||
annBoolExp rhsParser refFields whereExp
|
||||
annBoolExp rhsParser rootTable refFields whereExp
|
||||
return $ BoolExists $ GExists refqt annWhereExp
|
||||
BoolFld fld -> BoolFld <$> annColExp rhsParser fim fld
|
||||
BoolFld fld -> BoolFld <$> annColExp rhsParser rootTable fim fld
|
||||
where
|
||||
procExps = mapM (annBoolExp rhsParser fim)
|
||||
procExps = mapM (annBoolExp rhsParser rootTable fim)
|
||||
|
||||
annColExp
|
||||
:: (QErrM m, TableCoreInfoRM b m, BackendMetadata b)
|
||||
=> ValueParser b m v
|
||||
-> TableName b
|
||||
-> FieldInfoMap (FieldInfo b)
|
||||
-> ColExp
|
||||
-> m (AnnBoolExpFld b v)
|
||||
annColExp rhsParser colInfoMap (ColExp fieldName colVal) = do
|
||||
annColExp rhsParser rootTable colInfoMap (ColExp fieldName colVal) = do
|
||||
colInfo <- askFieldInfo colInfoMap fieldName
|
||||
case colInfo of
|
||||
FIColumn pgi -> AVCol pgi <$> parseBoolExpOperations rhsParser colInfoMap pgi colVal
|
||||
FIColumn pgi -> AVCol pgi <$> parseBoolExpOperations rhsParser rootTable colInfoMap pgi colVal
|
||||
FIRelationship relInfo -> do
|
||||
relBoolExp <- decodeValue colVal
|
||||
relFieldInfoMap <- askFieldInfoMapSource $ riRTable relInfo
|
||||
annRelBoolExp <- annBoolExp rhsParser relFieldInfoMap $
|
||||
annRelBoolExp <- annBoolExp rhsParser rootTable relFieldInfoMap $
|
||||
unBoolExp relBoolExp
|
||||
return $ AVRel relInfo annRelBoolExp
|
||||
FIComputedField _ ->
|
||||
@ -139,7 +140,8 @@ mkFieldCompExp
|
||||
:: S.Qual -> FieldName -> OpExpG 'Postgres S.SQLExp -> S.BoolExp
|
||||
mkFieldCompExp qual lhsField = mkCompExp (mkQField lhsField)
|
||||
where
|
||||
mkQCol = S.SEQIdentifier . S.QIdentifier qual . toIdentifier
|
||||
mkQCol (col, Nothing) = S.SEQIdentifier $ S.QIdentifier qual $ toIdentifier col
|
||||
mkQCol (col, Just table) = S.SEQIdentifier $ S.mkQIdentifierTable table $ toIdentifier col
|
||||
mkQField = S.SEQIdentifier . S.QIdentifier qual . Identifier . getFieldNameTxt
|
||||
|
||||
mkCompExp :: SQLExpression 'Postgres -> OpExpG 'Postgres (SQLExpression 'Postgres) -> S.BoolExp
|
||||
|
@ -76,7 +76,7 @@ procBoolExp
|
||||
-> BoolExp b
|
||||
-> m (AnnBoolExpPartialSQL b, [SchemaDependency])
|
||||
procBoolExp source tn fieldInfoMap be = do
|
||||
abe <- annBoolExp parseCollectableType fieldInfoMap $ unBoolExp be
|
||||
abe <- annBoolExp parseCollectableType tn fieldInfoMap $ unBoolExp be
|
||||
let deps = getBoolExpDeps source tn abe
|
||||
return (abe, deps)
|
||||
|
||||
|
@ -91,7 +91,7 @@ validateCountQWith sessVarBldr prepValBldr (CountQuery qt _ mDistCols mWhere) =
|
||||
-- convert the where clause
|
||||
annSQLBoolExp <- forM mWhere $ \be ->
|
||||
withPathK "where" $
|
||||
convBoolExp colInfoMap selPerm be sessVarBldr (valueParserWithCollectableType prepValBldr)
|
||||
convBoolExp colInfoMap selPerm be sessVarBldr qt (valueParserWithCollectableType prepValBldr)
|
||||
|
||||
resolvedSelFltr <- convAnnBoolExpPartialSQL sessVarBldr $
|
||||
spiFilter selPerm
|
||||
|
@ -68,7 +68,7 @@ validateDeleteQWith sessVarBldr prepValBldr
|
||||
|
||||
-- convert the where clause
|
||||
annSQLBoolExp <- withPathK "where" $
|
||||
convBoolExp fieldInfoMap selPerm rqlBE sessVarBldr (valueParserWithCollectableType prepValBldr)
|
||||
convBoolExp fieldInfoMap selPerm rqlBE sessVarBldr tableName (valueParserWithCollectableType prepValBldr)
|
||||
|
||||
resolvedDelFltr <- convAnnBoolExpPartialSQL sessVarBldr $
|
||||
dpiFilter delPerm
|
||||
|
@ -297,10 +297,11 @@ convBoolExp
|
||||
-> SelPermInfo b
|
||||
-> BoolExp b
|
||||
-> SessVarBldr b m
|
||||
-> TableName b
|
||||
-> ValueParser b m (SQLExpression b)
|
||||
-> m (AnnBoolExpSQL b)
|
||||
convBoolExp cim spi be sessVarBldr rhsParser = do
|
||||
abe <- annBoolExp rhsParser cim $ unBoolExp be
|
||||
convBoolExp cim spi be sessVarBldr rootTable rhsParser = do
|
||||
abe <- annBoolExp rhsParser rootTable cim $ unBoolExp be
|
||||
checkSelPerm spi sessVarBldr abe
|
||||
|
||||
dmlTxErrorHandler :: Q.PGTxErr -> QErr
|
||||
|
@ -206,7 +206,7 @@ convSelectQ table fieldInfoMap selPermInfo selQ sessVarBldr prepValBldr = do
|
||||
-- Convert where clause
|
||||
wClause <- forM (sqWhere selQ) $ \boolExp ->
|
||||
withPathK "where" $
|
||||
convBoolExp fieldInfoMap selPermInfo boolExp sessVarBldr prepValBldr
|
||||
convBoolExp fieldInfoMap selPermInfo boolExp sessVarBldr table prepValBldr
|
||||
|
||||
annFlds <- withPathK "columns" $
|
||||
indexedForM (sqColumns selQ) $ \case
|
||||
|
@ -155,7 +155,7 @@ validateUpdateQueryWith sessVarBldr prepValBldr uq = do
|
||||
|
||||
-- convert the where clause
|
||||
annSQLBoolExp <- withPathK "where" $
|
||||
convBoolExp fieldInfoMap selPerm (uqWhere uq) sessVarBldr prepValBldr
|
||||
convBoolExp fieldInfoMap selPerm (uqWhere uq) sessVarBldr tableName prepValBldr
|
||||
|
||||
resolvedUpdFltr <- convAnnBoolExpPartialSQL sessVarBldr $
|
||||
upiFilter updPerm
|
||||
|
@ -235,12 +235,17 @@ data OpExpG (b :: BackendType) a
|
||||
| ALIKE !a -- LIKE
|
||||
| ANLIKE !a -- NOT LIKE
|
||||
|
||||
| CEQ !(Column b)
|
||||
| CNE !(Column b)
|
||||
| CGT !(Column b)
|
||||
| CLT !(Column b)
|
||||
| CGTE !(Column b)
|
||||
| CLTE !(Column b)
|
||||
-- column comparison operators, the (Maybe (TableName b))
|
||||
-- is for setting the root table if there's a comparison
|
||||
-- of a relationship column with a column of the root table
|
||||
-- it will be set, otherwise it will be Nothing
|
||||
|
||||
| CEQ !(Column b, Maybe (TableName b))
|
||||
| CNE !(Column b, Maybe (TableName b))
|
||||
| CGT !(Column b, Maybe (TableName b))
|
||||
| CLT !(Column b, Maybe (TableName b))
|
||||
| CGTE !(Column b, Maybe (TableName b))
|
||||
| CLTE !(Column b, Maybe (TableName b))
|
||||
|
||||
| ANISNULL -- IS NULL
|
||||
| ANISNOTNULL -- IS NOT NULL
|
||||
@ -284,7 +289,7 @@ instance (Backend b, ToJSONKeyValue (BooleanOperators b a), ToJSON a) => ToJSONK
|
||||
|
||||
ABackendSpecific b -> toJSONKeyValue b
|
||||
|
||||
opExpDepCol :: OpExpG backend a -> Maybe (Column backend)
|
||||
opExpDepCol :: OpExpG backend a -> Maybe (Column backend, Maybe (TableName backend))
|
||||
opExpDepCol = \case
|
||||
CEQ c -> Just c
|
||||
CNE c -> Just c
|
||||
|
@ -178,6 +178,7 @@ data ColumnInfo (b :: BackendType)
|
||||
, pgiDescription :: !(Maybe G.Description)
|
||||
} deriving (Generic)
|
||||
deriving instance Backend b => Eq (ColumnInfo b)
|
||||
deriving instance Backend b => Show (ColumnInfo b)
|
||||
instance Backend b => Cacheable (ColumnInfo b)
|
||||
instance Backend b => NFData (ColumnInfo b)
|
||||
instance Backend b => Hashable (ColumnInfo b)
|
||||
|
@ -110,6 +110,7 @@ data ComputedFieldInfo (b :: BackendType)
|
||||
, _cfiComment :: !(Maybe Text)
|
||||
} deriving (Generic)
|
||||
deriving instance (Backend b) => Eq (ComputedFieldInfo b)
|
||||
deriving instance (Backend b) => Show (ComputedFieldInfo b)
|
||||
instance (Backend b) => Cacheable (ComputedFieldInfo b)
|
||||
instance (Backend b) => ToJSON (ComputedFieldInfo b) where
|
||||
-- spelling out the JSON instance in order to skip the Trees That Grow field
|
||||
|
@ -86,6 +86,7 @@ class (Backend b) => BackendMetadata (b :: BackendType) where
|
||||
parseBoolExpOperations
|
||||
:: (MonadError QErr m, TableCoreInfoRM b m)
|
||||
=> ValueParser b m v
|
||||
-> TableName b
|
||||
-> FieldInfoMap (FieldInfo b)
|
||||
-> ColumnInfo b
|
||||
-> Value
|
||||
|
@ -75,6 +75,7 @@ data RemoteFieldInfo (b :: BackendType)
|
||||
-- ^ Name of the table and its source
|
||||
} deriving (Generic)
|
||||
deriving instance Backend b => Eq (RemoteFieldInfo b)
|
||||
deriving instance Backend b => Show (RemoteFieldInfo b)
|
||||
instance Backend b => Cacheable (RemoteFieldInfo b)
|
||||
|
||||
graphQLValueToJSON :: G.Value Void -> Value
|
||||
|
@ -484,20 +484,23 @@ getColExpDeps
|
||||
-> TableName b
|
||||
-> AnnBoolExpFld b (PartialSQLExp b)
|
||||
-> [SchemaDependency]
|
||||
getColExpDeps source tn = \case
|
||||
getColExpDeps source tableName = \case
|
||||
AVCol colInfo opExps ->
|
||||
let cn = pgiColumn colInfo
|
||||
let columnName = pgiColumn colInfo
|
||||
colDepReason = bool DRSessionVariable DROnType $ any hasStaticExp opExps
|
||||
colDep = mkColDep colDepReason source tn cn
|
||||
colDep = mkColDep colDepReason source tableName columnName
|
||||
depColsInOpExp = mapMaybe opExpDepCol opExps
|
||||
colDepsInOpExp = map (mkColDep DROnType source tn) depColsInOpExp
|
||||
colDepsInOpExp = do
|
||||
(col, rootTable) <- depColsInOpExp
|
||||
pure $ mkColDep DROnType source (fromMaybe tableName rootTable) col
|
||||
in colDep:colDepsInOpExp
|
||||
AVRel relInfo relBoolExp ->
|
||||
let rn = riName relInfo
|
||||
relTN = riRTable relInfo
|
||||
pd = SchemaDependency
|
||||
(SOSourceObj source
|
||||
$ AB.mkAnyBackend
|
||||
$ SOITableObj tn (TORel rn))
|
||||
DROnType
|
||||
in pd : getBoolExpDeps source relTN relBoolExp
|
||||
let relationshipName = riName relInfo
|
||||
relationshipTable = riRTable relInfo
|
||||
schemaDependency =
|
||||
SchemaDependency
|
||||
(SOSourceObj source
|
||||
$ AB.mkAnyBackend
|
||||
$ SOITableObj tableName (TORel relationshipName))
|
||||
DROnType
|
||||
in schemaDependency : getBoolExpDeps source relationshipTable relBoolExp
|
||||
|
@ -95,6 +95,7 @@ data FieldInfo (b :: BackendType)
|
||||
| FIComputedField !(ComputedFieldInfo b)
|
||||
| FIRemoteRelationship !(RemoteFieldInfo b)
|
||||
deriving (Generic)
|
||||
deriving instance Backend b => Show (FieldInfo b)
|
||||
deriving instance Backend b => Eq (FieldInfo b)
|
||||
instance Backend b => Cacheable (FieldInfo b)
|
||||
instance Backend b => ToJSON (FieldInfo b) where
|
||||
|
@ -0,0 +1,36 @@
|
||||
# Column comparison of a relationship table's column with a compatible root table's column
|
||||
- description: Trying to order more number of an item than present in the inventory should fail
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
headers:
|
||||
X-Hasura-Role: user
|
||||
response:
|
||||
errors:
|
||||
- extensions:
|
||||
path: $.selectionSet.insert_order_cart.args.objects
|
||||
code: permission-error
|
||||
message: check constraint of an insert/update permission has failed
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
insert_order_cart( objects: { item_id: 1, quantity: 106 }) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
|
||||
- description: When the order quantity is less than the item's inventory quantity, it should succeed
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
headers:
|
||||
X-Hasura-Role: user
|
||||
response:
|
||||
data:
|
||||
insert_order_cart:
|
||||
affected_rows: 1
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
insert_order_cart( objects: { item_id: 1, quantity: 6 }) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
@ -83,6 +83,17 @@ args:
|
||||
name text not null,
|
||||
added_by text not null
|
||||
);
|
||||
create table items (
|
||||
id serial primary key,
|
||||
name text,
|
||||
quantity int
|
||||
);
|
||||
create table order_cart (
|
||||
id serial primary key,
|
||||
item_id int not null references items(id),
|
||||
quantity int
|
||||
);
|
||||
|
||||
|
||||
- type: track_table
|
||||
args:
|
||||
@ -538,3 +549,36 @@ args:
|
||||
check:
|
||||
name:
|
||||
_ne: ''
|
||||
|
||||
- type: track_table
|
||||
args:
|
||||
schema: public
|
||||
name: items
|
||||
|
||||
- type: track_table
|
||||
args:
|
||||
schema: public
|
||||
name: order_cart
|
||||
|
||||
- type: create_object_relationship
|
||||
args:
|
||||
table: order_cart
|
||||
name: item
|
||||
using:
|
||||
foreign_key_constraint_on: item_id
|
||||
|
||||
# an user can add an item in the order_cart
|
||||
# iff there are enough of the them present
|
||||
- type: create_insert_permission
|
||||
args:
|
||||
table: order_cart
|
||||
role: user
|
||||
permission:
|
||||
columns: [item_id, quantity]
|
||||
check:
|
||||
item:
|
||||
quantity:
|
||||
_cgte:
|
||||
- $
|
||||
- quantity
|
||||
set: {}
|
||||
|
@ -22,4 +22,6 @@ args:
|
||||
drop table "user";
|
||||
drop table account;
|
||||
drop table leads;
|
||||
drop table order_cart;
|
||||
drop table items;
|
||||
cascade: true
|
||||
|
@ -49,4 +49,11 @@ args:
|
||||
name: Resident 6
|
||||
age: 22
|
||||
|
||||
#Insert users
|
||||
- type: insert
|
||||
args:
|
||||
table: items
|
||||
objects:
|
||||
- name: Apsara Pencil
|
||||
quantity: 10
|
||||
- name: Reynolds Trimax
|
||||
quantity: 1323
|
||||
|
@ -24,3 +24,8 @@ args:
|
||||
|
||||
delete from "user";
|
||||
SELECT setval('"user_id_seq"', 1, FALSE);
|
||||
|
||||
delete from order_cart;
|
||||
|
||||
delete from items;
|
||||
SELECT setval('items_id_seq', 1, FALSE);
|
||||
|
@ -205,6 +205,9 @@ class TestGraphqlInsertPermission:
|
||||
def test_check_set_headers_while_doing_upsert(self,hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/leads_upsert_check_with_headers.yaml")
|
||||
|
||||
def test_column_comparison_across_different_tables(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/column_comparison_across_tables.yaml")
|
||||
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return "queries/graphql_mutation/insert/permissions"
|
||||
|
Loading…
Reference in New Issue
Block a user