server/postgres: Support computed fields in permission check/filter

https://github.com/hasura/graphql-engine-mono/pull/1697

GitOrigin-RevId: 6cdf8acc90d3fd97d20a3ee68c84306c3f589370
This commit is contained in:
Rakesh Emmadi 2021-07-28 13:39:32 +05:30 committed by hasura-bot
parent ca567bf8cb
commit a63fa18d9c
23 changed files with 300 additions and 83 deletions

View File

@ -3,6 +3,7 @@
## Next release ## Next release
(Add entries below in the order of server, console, cli, docs, others) (Add entries below in the order of server, console, cli, docs, others)
- server: Support computed fields in permission check/filter (close #7102)
- server: support computed fields in query 'order_by' (close #7103) - server: support computed fields in query 'order_by' (close #7103)
- server: log warning if there are errors while executing clean up actions after "drop source" (previously it would throw an error) - server: log warning if there are errors while executing clean up actions after "drop source" (previously it would throw an error)
- server: Fixed a bug where MSSQL and BigQuery would ignore environment variables set from the console - server: Fixed a bug where MSSQL and BigQuery would ignore environment variables set from the console

View File

@ -5,6 +5,8 @@ import Hasura.Prelude
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.HashMap.Strict as Map import qualified Data.HashMap.Strict as Map
import Data.Text.Extended
import Hasura.Backends.BigQuery.Types import Hasura.Backends.BigQuery.Types
import Hasura.Base.Error import Hasura.Base.Error
import Hasura.RQL.IR.BoolExp import Hasura.RQL.IR.BoolExp
@ -20,12 +22,11 @@ parseBoolExpOperations
=> ValueParser 'BigQuery m v => ValueParser 'BigQuery m v
-> TableName -> TableName
-> FieldInfoMap (FieldInfo 'BigQuery) -> FieldInfoMap (FieldInfo 'BigQuery)
-> ColumnInfo 'BigQuery -> ColumnReference 'BigQuery
-> J.Value -> J.Value
-> m [OpExpG 'BigQuery v] -> m [OpExpG 'BigQuery v]
parseBoolExpOperations rhsParser _table _fields columnInfo value = parseBoolExpOperations rhsParser _table _fields columnRef value =
withPathK (columnName $ pgiColumn columnInfo) $ withPathK (toTxt columnRef) $ parseOperations (columnReferenceType columnRef) value
parseOperations (pgiType columnInfo) value
where where
parseWithTy ty = rhsParser (CollectableTypeScalar ty) parseWithTy ty = rhsParser (CollectableTypeScalar ty)

View File

@ -6,7 +6,7 @@ import qualified Data.Aeson as J
import qualified Data.HashMap.Strict as Map import qualified Data.HashMap.Strict as Map
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Extended (dquote, (<<>)) import Data.Text.Extended (dquote, toTxt, (<<>))
import Hasura.Backends.MSSQL.Types hiding (ColumnType) import Hasura.Backends.MSSQL.Types hiding (ColumnType)
import Hasura.Base.Error import Hasura.Base.Error
@ -22,12 +22,11 @@ parseBoolExpOperations
=> ValueParser 'MSSQL m v => ValueParser 'MSSQL m v
-> TableName -> TableName
-> FieldInfoMap (FieldInfo 'MSSQL) -> FieldInfoMap (FieldInfo 'MSSQL)
-> ColumnInfo 'MSSQL -> ColumnReference 'MSSQL
-> J.Value -> J.Value
-> m [OpExpG 'MSSQL v] -> m [OpExpG 'MSSQL v]
parseBoolExpOperations rhsParser _table _fields columnInfo value = parseBoolExpOperations rhsParser _table _fields columnRef value =
withPathK (columnNameText $ pgiColumn columnInfo) $ withPathK (toTxt columnRef) $ parseOperations (columnReferenceType columnRef) value
parseOperations (pgiType columnInfo) value
where where
parseWithTy ty = rhsParser (CollectableTypeScalar ty) parseWithTy ty = rhsParser (CollectableTypeScalar ty)
@ -88,7 +87,7 @@ parseBoolExpOperations rhsParser _table _fields columnInfo value =
x -> throw400 UnexpectedPayload $ "Unknown operator : " <> x x -> throw400 UnexpectedPayload $ "Unknown operator : " <> x
where where
colTy = pgiType columnInfo colTy = columnReferenceType columnRef
parseOne = parseWithTy columnType val parseOne = parseWithTy columnType val
parseManyWithType ty = rhsParser (CollectableTypeArray ty) val parseManyWithType ty = rhsParser (CollectableTypeArray ty) val

View File

@ -22,23 +22,6 @@ import Hasura.SQL.Backend
import Hasura.SQL.Types import Hasura.SQL.Types
-- | Represents a reference to a Postgres column, possibly casted an arbitrary
-- number of times. Used within 'parseOperationsExpression' for bookkeeping.
data ColumnReference (b :: BackendType)
= ColumnReferenceColumn !(ColumnInfo b)
| ColumnReferenceCast !(ColumnReference b) !(ColumnType b)
columnReferenceType :: ColumnReference backend -> ColumnType backend
columnReferenceType = \case
ColumnReferenceColumn column -> pgiType column
ColumnReferenceCast _ targetType -> targetType
instance Backend b => ToTxt (ColumnReference b) where
toTxt = \case
ColumnReferenceColumn column -> toTxt $ pgiColumn column
ColumnReferenceCast reference targetType ->
toTxt reference <> "::" <> toTxt targetType
parseBoolExpOperations parseBoolExpOperations
:: forall pgKind m v :: forall pgKind m v
. ( Backend ('Postgres pgKind) . ( Backend ('Postgres pgKind)
@ -48,19 +31,18 @@ parseBoolExpOperations
=> ValueParser ('Postgres pgKind) m v => ValueParser ('Postgres pgKind) m v
-> QualifiedTable -> QualifiedTable
-> FieldInfoMap (FieldInfo ('Postgres pgKind)) -> FieldInfoMap (FieldInfo ('Postgres pgKind))
-> ColumnInfo ('Postgres pgKind) -> ColumnReference ('Postgres pgKind)
-> Value -> Value
-> m [OpExpG ('Postgres pgKind) v] -> m [OpExpG ('Postgres pgKind) v]
parseBoolExpOperations rhsParser rootTable fim columnInfo value = do parseBoolExpOperations rhsParser rootTable fim columnRef value = do
restrictJSONColumn restrictJSONColumn
withPathK (getPGColTxt $ pgiColumn columnInfo) $ withPathK (toTxt columnRef) $ parseOperations columnRef value
parseOperations (ColumnReferenceColumn columnInfo) value
where where
restrictJSONColumn :: m () restrictJSONColumn :: m ()
restrictJSONColumn = case columnInfo of restrictJSONColumn = case columnReferenceType columnRef of
ColumnInfo _ _ _ (ColumnScalar PGJSON) _ _ -> ColumnScalar PGJSON ->
throwError (err400 UnexpectedPayload "JSON column can not be part of boolean expression") throwError (err400 UnexpectedPayload "JSON column can not be part of boolean expression")
_ -> pure () _ -> pure ()
parseOperations :: ColumnReference ('Postgres pgKind) -> Value -> m [OpExpG ('Postgres pgKind) v] parseOperations :: ColumnReference ('Postgres pgKind) -> Value -> m [OpExpG ('Postgres pgKind) v]
parseOperations column = \case parseOperations column = \case

View File

@ -3,6 +3,7 @@ module Hasura.Backends.Postgres.Translate.BoolExp
( toSQLBoolExp ( toSQLBoolExp
, getBoolExpDeps , getBoolExpDeps
, annBoolExp , annBoolExp
, BoolExpRHSParser(..)
) where ) where
import Hasura.Prelude import Hasura.Prelude
@ -18,6 +19,13 @@ import Hasura.RQL.Types
import Hasura.SQL.Types import Hasura.SQL.Types
-- | Context to parse a RHS value in a boolean expression
data BoolExpRHSParser (b :: BackendType) m v
= BoolExpRHSParser
{ _berpValueParser :: !(ValueParser b m v) -- ^ Parse a JSON value with enforcing a column type
, _berpSessionValue :: !v -- ^ Required for a computed field SQL function with session argument
}
-- This convoluted expression instead of col = val -- This convoluted expression instead of col = val
-- to handle the case of col : null -- to handle the case of col : null
equalsBoolExpBuilder :: SQLExpression ('Postgres pgKind) -> SQLExpression ('Postgres pgKind) -> S.BoolExp equalsBoolExpBuilder :: SQLExpression ('Postgres pgKind) -> SQLExpression ('Postgres pgKind) -> S.BoolExp
@ -36,7 +44,7 @@ notEqualsBoolExpBuilder qualColExp rhsExp =
annBoolExp annBoolExp
:: (QErrM m, TableCoreInfoRM b m, BackendMetadata b) :: (QErrM m, TableCoreInfoRM b m, BackendMetadata b)
=> ValueParser b m v => BoolExpRHSParser b m v
-> TableName b -> TableName b
-> FieldInfoMap (FieldInfo b) -> FieldInfoMap (FieldInfo b)
-> GBoolExp b ColExp -> GBoolExp b ColExp
@ -49,8 +57,7 @@ annBoolExp rhsParser rootTable fim boolExp =
BoolExists (GExists refqt whereExp) -> BoolExists (GExists refqt whereExp) ->
withPathK "_exists" $ do withPathK "_exists" $ do
refFields <- withPathK "_table" $ askFieldInfoMapSource refqt refFields <- withPathK "_table" $ askFieldInfoMapSource refqt
annWhereExp <- withPathK "_where" $ annWhereExp <- withPathK "_where" $ annBoolExp rhsParser rootTable refFields whereExp
annBoolExp rhsParser rootTable refFields whereExp
return $ BoolExists $ GExists refqt annWhereExp return $ BoolExists $ GExists refqt annWhereExp
BoolFld fld -> BoolFld <$> annColExp rhsParser rootTable fim fld BoolFld fld -> BoolFld <$> annColExp rhsParser rootTable fim fld
where where
@ -58,7 +65,7 @@ annBoolExp rhsParser rootTable fim boolExp =
annColExp annColExp
:: (QErrM m, TableCoreInfoRM b m, BackendMetadata b) :: (QErrM m, TableCoreInfoRM b m, BackendMetadata b)
=> ValueParser b m v => BoolExpRHSParser b m v
-> TableName b -> TableName b
-> FieldInfoMap (FieldInfo b) -> FieldInfoMap (FieldInfo b)
-> ColExp -> ColExp
@ -66,15 +73,33 @@ annColExp
annColExp rhsParser rootTable colInfoMap (ColExp fieldName colVal) = do annColExp rhsParser rootTable colInfoMap (ColExp fieldName colVal) = do
colInfo <- askFieldInfo colInfoMap fieldName colInfo <- askFieldInfo colInfoMap fieldName
case colInfo of case colInfo of
FIColumn pgi -> AVColumn pgi <$> parseBoolExpOperations rhsParser rootTable colInfoMap pgi colVal FIColumn pgi -> AVColumn pgi <$> parseBoolExpOperations (_berpValueParser rhsParser) rootTable colInfoMap (ColumnReferenceColumn pgi) colVal
FIRelationship relInfo -> do FIRelationship relInfo -> do
relBoolExp <- decodeValue colVal relBoolExp <- decodeValue colVal
relFieldInfoMap <- askFieldInfoMapSource $ riRTable relInfo relFieldInfoMap <- askFieldInfoMapSource $ riRTable relInfo
annRelBoolExp <- annBoolExp rhsParser rootTable relFieldInfoMap $ annRelBoolExp <- annBoolExp rhsParser rootTable relFieldInfoMap $ unBoolExp relBoolExp
unBoolExp relBoolExp
return $ AVRelationship relInfo annRelBoolExp return $ AVRelationship relInfo annRelBoolExp
FIComputedField _ ->
throw400 UnexpectedPayload "Computed columns can not be part of the where clause" FIComputedField ComputedFieldInfo{..} -> do
let ComputedFieldFunction{..} = _cfiFunction
case toList _cffInputArgs of
[] -> do
let hasuraSession = _berpSessionValue rhsParser
sessionArgPresence = mkSessionArgumentPresence hasuraSession _cffSessionArgument _cffTableArgument
AVComputedField . AnnComputedFieldBoolExp _cfiXComputedFieldInfo _cfiName _cffName sessionArgPresence
<$> case _cfiReturnType of
CFRScalar scalarType -> CFBEScalar
<$> parseBoolExpOperations (_berpValueParser rhsParser) rootTable colInfoMap (ColumnReferenceComputedField _cfiName scalarType) colVal
CFRSetofTable table -> do
tableBoolExp <- decodeValue colVal
tableFieldInfoMap <- askFieldInfoMapSource table
annTableBoolExp <- annBoolExp rhsParser table tableFieldInfoMap $ unBoolExp tableBoolExp
pure $ CFBETable table annTableBoolExp
_ -> throw400 UnexpectedPayload
"Computed columns with input arguments can not be part of the where clause"
-- TODO Rakesh (from master) -- TODO Rakesh (from master)
FIRemoteRelationship{} -> FIRemoteRelationship{} ->
throw400 UnexpectedPayload "remote field unsupported" throw400 UnexpectedPayload "remote field unsupported"

View File

@ -86,13 +86,8 @@ boolExp sourceName tableInfo selectPermissions = memoizeOn 'boolExp (sourceName,
-- For a computed field to qualify in boolean expression it shouldn't have any input arguments -- For a computed field to qualify in boolean expression it shouldn't have any input arguments
case toList _cffInputArgs of case toList _cffInputArgs of
[] -> do [] -> do
let sessionArgPresence = case _cffSessionArgument of let sessionArgPresence =
Nothing -> SAPNotPresent mkSessionArgumentPresence P.UVSession _cffSessionArgument _cffTableArgument
Just _ -> case _cffTableArgument of
FTAFirst -> SAPSecond P.UVSession -- If table argument is first, then session argument will be second
FTANamed _ 0 -> SAPSecond P.UVSession -- Index 0 => table argument is first
FTANamed{} -> SAPFirst P.UVSession -- If table argument is second, then session argument will be firest
fmap (AVComputedField . AnnComputedFieldBoolExp _cfiXComputedFieldInfo _cfiName _cffName sessionArgPresence) fmap (AVComputedField . AnnComputedFieldBoolExp _cfiXComputedFieldInfo _cfiName _cffName sessionArgPresence)
<$> case _cfiReturnType of <$> case _cfiReturnType of
CFRScalar scalarType -> lift $ fmap CFBEScalar <$> comparisonExps @b (ColumnScalar scalarType) CFRScalar scalarType -> lift $ fmap CFBEScalar <$> comparisonExps @b (ColumnScalar scalarType)

View File

@ -47,6 +47,7 @@ textToName textName = G.mkName textName `onNothing` throw400 ValidationFailed
partialSQLExpToUnpreparedValue :: PartialSQLExp b -> P.UnpreparedValue b partialSQLExpToUnpreparedValue :: PartialSQLExp b -> P.UnpreparedValue b
partialSQLExpToUnpreparedValue (PSESessVar pftype var) = P.UVSessionVar pftype var partialSQLExpToUnpreparedValue (PSESessVar pftype var) = P.UVSessionVar pftype var
partialSQLExpToUnpreparedValue PSESession = P.UVSession
partialSQLExpToUnpreparedValue (PSESQLExp sqlExp) = P.UVLiteral sqlExp partialSQLExpToUnpreparedValue (PSESQLExp sqlExp) = P.UVLiteral sqlExp
mapField mapField

View File

@ -79,7 +79,8 @@ procBoolExp
-> BoolExp b -> BoolExp b
-> m (AnnBoolExpPartialSQL b, [SchemaDependency]) -> m (AnnBoolExpPartialSQL b, [SchemaDependency])
procBoolExp source tn fieldInfoMap be = do procBoolExp source tn fieldInfoMap be = do
abe <- annBoolExp parseCollectableType tn fieldInfoMap $ unBoolExp be let rhsParser = BoolExpRHSParser parseCollectableType PSESession
abe <- annBoolExp rhsParser tn fieldInfoMap $ unBoolExp be
let deps = getBoolExpDeps source tn abe let deps = getBoolExpDeps source tn abe
return (abe, deps) return (abe, deps)

View File

@ -71,7 +71,7 @@ mkSQLCount (CountQueryP1 tn (permFltr, mWc) mDistCols) =
-- SELECT count(*) FROM (SELECT * FROM .. WHERE ..) r; -- SELECT count(*) FROM (SELECT * FROM .. WHERE ..) r;
validateCountQWith validateCountQWith
:: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m) :: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m)
=> SessVarBldr ('Postgres 'Vanilla) m => SessionVariableBuilder ('Postgres 'Vanilla) m
-> (ColumnType ('Postgres 'Vanilla) -> Value -> m S.SQLExp) -> (ColumnType ('Postgres 'Vanilla) -> Value -> m S.SQLExp)
-> CountQuery -> CountQuery
-> m CountQueryP1 -> m CountQueryP1

View File

@ -34,7 +34,7 @@ import Hasura.Session
validateDeleteQWith validateDeleteQWith
:: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m) :: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m)
=> SessVarBldr ('Postgres 'Vanilla) m => SessionVariableBuilder ('Postgres 'Vanilla) m
-> (ColumnType ('Postgres 'Vanilla) -> Value -> m S.SQLExp) -> (ColumnType ('Postgres 'Vanilla) -> Value -> m S.SQLExp)
-> DeleteQuery -> DeleteQuery
-> m (AnnDel ('Postgres 'Vanilla)) -> m (AnnDel ('Postgres 'Vanilla))

View File

@ -68,7 +68,7 @@ validateInpCols inpCols updColsPerm = forM_ inpCols $ \inpCol ->
buildConflictClause buildConflictClause
:: (UserInfoM m, QErrM m) :: (UserInfoM m, QErrM m)
=> SessVarBldr ('Postgres 'Vanilla) m => SessionVariableBuilder ('Postgres 'Vanilla) m
-> TableInfo ('Postgres 'Vanilla) -> TableInfo ('Postgres 'Vanilla)
-> [PGCol] -> [PGCol]
-> OnConflict -> OnConflict
@ -129,7 +129,7 @@ buildConflictClause sessVarBldr tableInfo inpCols (OnConflict mTCol mTCons act)
convInsertQuery convInsertQuery
:: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m) :: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m)
=> (Value -> m [InsObj ('Postgres 'Vanilla)]) => (Value -> m [InsObj ('Postgres 'Vanilla)])
-> SessVarBldr ('Postgres 'Vanilla) m -> SessionVariableBuilder ('Postgres 'Vanilla) m
-> (ColumnType ('Postgres 'Vanilla) -> Value -> m S.SQLExp) -> (ColumnType ('Postgres 'Vanilla) -> Value -> m S.SQLExp)
-> InsertQuery -> InsertQuery
-> m (InsertQueryP1 ('Postgres 'Vanilla)) -> m (InsertQueryP1 ('Postgres 'Vanilla))

View File

@ -1,7 +1,4 @@
module Hasura.RQL.DML.Internal where module Hasura.RQL.DML.Internal where
-- ( mkAdminRolePermInfo
-- , SessVarBldr
-- ) where
import Hasura.Prelude import Hasura.Prelude
@ -207,7 +204,11 @@ fetchRelTabInfo refTabName =
modifyErrAndSet500 ("foreign " <> ) $ modifyErrAndSet500 ("foreign " <> ) $
askTabInfoSource refTabName askTabInfoSource refTabName
type SessVarBldr b m = SessionVarType b -> SessionVariable -> m (SQLExpression b) data SessionVariableBuilder b m
= SessionVariableBuilder
{ _svbCurrentSession :: !(SQLExpression b)
, _svbVariableParser :: !(SessionVarType b -> SessionVariable -> m (SQLExpression b))
}
fetchRelDet fetchRelDet
:: (UserInfoM m, QErrM m, TableInfoRM b m, Backend b) :: (UserInfoM m, QErrM m, TableInfoRM b m, Backend b)
@ -234,7 +235,7 @@ fetchRelDet relName refTabName = do
checkOnColExp checkOnColExp
:: (UserInfoM m, QErrM m, TableInfoRM b m, Backend b) :: (UserInfoM m, QErrM m, TableInfoRM b m, Backend b)
=> SelPermInfo b => SelPermInfo b
-> SessVarBldr b m -> SessionVariableBuilder b m
-> AnnBoolExpFldSQL b -> AnnBoolExpFldSQL b
-> m (AnnBoolExpFldSQL b) -> m (AnnBoolExpFldSQL b)
checkOnColExp spi sessVarBldr annFld = case annFld of checkOnColExp spi sessVarBldr annFld = case annFld of
@ -267,7 +268,7 @@ checkOnColExp spi sessVarBldr annFld = case annFld of
convAnnBoolExpPartialSQL convAnnBoolExpPartialSQL
:: (Applicative f, Backend backend) :: (Applicative f, Backend backend)
=> SessVarBldr backend f => SessionVariableBuilder backend f
-> AnnBoolExpPartialSQL backend -> AnnBoolExpPartialSQL backend
-> f (AnnBoolExpSQL backend) -> f (AnnBoolExpSQL backend)
convAnnBoolExpPartialSQL f = convAnnBoolExpPartialSQL f =
@ -275,7 +276,7 @@ convAnnBoolExpPartialSQL f =
convAnnColumnCaseBoolExpPartialSQL convAnnColumnCaseBoolExpPartialSQL
:: (Applicative f, Backend backend) :: (Applicative f, Backend backend)
=> SessVarBldr backend f => SessionVariableBuilder backend f
-> AnnColumnCaseBoolExpPartialSQL backend -> AnnColumnCaseBoolExpPartialSQL backend
-> f (AnnColumnCaseBoolExp backend (SQLExpression backend)) -> f (AnnColumnCaseBoolExp backend (SQLExpression backend))
convAnnColumnCaseBoolExpPartialSQL f = convAnnColumnCaseBoolExpPartialSQL f =
@ -283,17 +284,18 @@ convAnnColumnCaseBoolExpPartialSQL f =
convPartialSQLExp convPartialSQLExp
:: (Applicative f) :: (Applicative f)
=> SessVarBldr backend f => SessionVariableBuilder backend f
-> PartialSQLExp backend -> PartialSQLExp backend
-> f (SQLExpression backend) -> f (SQLExpression backend)
convPartialSQLExp f = \case convPartialSQLExp sessVarBldr = \case
PSESQLExp sqlExp -> pure sqlExp PSESQLExp sqlExp -> pure sqlExp
PSESessVar colTy sessionVariable -> f colTy sessionVariable PSESession -> pure $ _svbCurrentSession sessVarBldr
PSESessVar colTy sessionVariable -> (_svbVariableParser sessVarBldr) colTy sessionVariable
sessVarFromCurrentSetting sessVarFromCurrentSetting
:: (Applicative f) => CollectableType PGScalarType -> SessionVariable -> f S.SQLExp :: (Applicative f) => SessionVariableBuilder ('Postgres pgKind) f
sessVarFromCurrentSetting pgType sessVar = sessVarFromCurrentSetting =
pure $ sessVarFromCurrentSetting' pgType sessVar SessionVariableBuilder currentSession $ \ty var -> pure $ sessVarFromCurrentSetting' ty var
sessVarFromCurrentSetting' :: CollectableType PGScalarType -> SessionVariable -> S.SQLExp sessVarFromCurrentSetting' :: CollectableType PGScalarType -> SessionVariable -> S.SQLExp
sessVarFromCurrentSetting' ty sessVar = sessVarFromCurrentSetting' ty sessVar =
@ -329,7 +331,7 @@ currentSession = S.SEUnsafe "current_setting('hasura.user')::json"
checkSelPerm checkSelPerm
:: (UserInfoM m, QErrM m, TableInfoRM b m, Backend b) :: (UserInfoM m, QErrM m, TableInfoRM b m, Backend b)
=> SelPermInfo b => SelPermInfo b
-> SessVarBldr b m -> SessionVariableBuilder b m
-> AnnBoolExpSQL b -> AnnBoolExpSQL b
-> m (AnnBoolExpSQL b) -> m (AnnBoolExpSQL b)
checkSelPerm spi sessVarBldr = checkSelPerm spi sessVarBldr =
@ -340,12 +342,13 @@ convBoolExp
=> FieldInfoMap (FieldInfo b) => FieldInfoMap (FieldInfo b)
-> SelPermInfo b -> SelPermInfo b
-> BoolExp b -> BoolExp b
-> SessVarBldr b m -> SessionVariableBuilder b m
-> TableName b -> TableName b
-> ValueParser b m (SQLExpression b) -> ValueParser b m (SQLExpression b)
-> m (AnnBoolExpSQL b) -> m (AnnBoolExpSQL b)
convBoolExp cim spi be sessVarBldr rootTable rhsParser = do convBoolExp cim spi be sessVarBldr rootTable rhsParser = do
abe <- annBoolExp rhsParser rootTable cim $ unBoolExp be let boolExpRHSParser = BoolExpRHSParser rhsParser $ _svbCurrentSession sessVarBldr
abe <- annBoolExp boolExpRHSParser rootTable cim $ unBoolExp be
checkSelPerm spi sessVarBldr abe checkSelPerm spi sessVarBldr abe
dmlTxErrorHandler :: Q.PGTxErr -> QErr dmlTxErrorHandler :: Q.PGTxErr -> QErr

View File

@ -118,7 +118,7 @@ resolveStar fim selPermInfo (SelectG selCols mWh mOb mLt mOf) = do
convOrderByElem convOrderByElem
:: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m) :: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m)
=> SessVarBldr ('Postgres 'Vanilla) m => SessionVariableBuilder ('Postgres 'Vanilla) m
-> (FieldInfoMap (FieldInfo ('Postgres 'Vanilla)), SelPermInfo ('Postgres 'Vanilla)) -> (FieldInfoMap (FieldInfo ('Postgres 'Vanilla)), SelPermInfo ('Postgres 'Vanilla))
-> OrderByCol -> OrderByCol
-> m (AnnotatedOrderByElement ('Postgres 'Vanilla) S.SQLExp) -> m (AnnotatedOrderByElement ('Postgres 'Vanilla) S.SQLExp)
@ -179,7 +179,7 @@ convSelectQ
-> FieldInfoMap (FieldInfo ('Postgres 'Vanilla)) -- Table information of current table -> FieldInfoMap (FieldInfo ('Postgres 'Vanilla)) -- Table information of current table
-> SelPermInfo ('Postgres 'Vanilla) -- Additional select permission info -> SelPermInfo ('Postgres 'Vanilla) -- Additional select permission info
-> SelectQExt ('Postgres 'Vanilla) -- Given Select Query -> SelectQExt ('Postgres 'Vanilla) -- Given Select Query
-> SessVarBldr ('Postgres 'Vanilla) m -> SessionVariableBuilder ('Postgres 'Vanilla) m
-> ValueParser ('Postgres 'Vanilla) m S.SQLExp -> ValueParser ('Postgres 'Vanilla) m S.SQLExp
-> m (AnnSimpleSelect ('Postgres 'Vanilla)) -> m (AnnSimpleSelect ('Postgres 'Vanilla))
convSelectQ table fieldInfoMap selPermInfo selQ sessVarBldr prepValBldr = do convSelectQ table fieldInfoMap selPermInfo selQ sessVarBldr prepValBldr = do
@ -250,7 +250,7 @@ convExtRel
-> RelName -> RelName
-> Maybe RelName -> Maybe RelName
-> SelectQExt ('Postgres 'Vanilla) -> SelectQExt ('Postgres 'Vanilla)
-> SessVarBldr ('Postgres 'Vanilla) m -> SessionVariableBuilder ('Postgres 'Vanilla) m
-> ValueParser ('Postgres 'Vanilla) m S.SQLExp -> ValueParser ('Postgres 'Vanilla) m S.SQLExp
-> m (Either (ObjectRelationSelect ('Postgres 'Vanilla)) (ArraySelect ('Postgres 'Vanilla))) -> m (Either (ObjectRelationSelect ('Postgres 'Vanilla)) (ArraySelect ('Postgres 'Vanilla)))
convExtRel fieldInfoMap relName mAlias selQ sessVarBldr prepValBldr = do convExtRel fieldInfoMap relName mAlias selQ sessVarBldr prepValBldr = do
@ -288,7 +288,7 @@ convSelectQuery
, TableInfoRM ('Postgres 'Vanilla) m , TableInfoRM ('Postgres 'Vanilla) m
, HasServerConfigCtx m , HasServerConfigCtx m
) )
=> SessVarBldr ('Postgres 'Vanilla) m => SessionVariableBuilder ('Postgres 'Vanilla) m
-> ValueParser ('Postgres 'Vanilla) m S.SQLExp -> ValueParser ('Postgres 'Vanilla) m S.SQLExp
-> SelectQuery -> SelectQuery
-> m (AnnSimpleSelect ('Postgres 'Vanilla)) -> m (AnnSimpleSelect ('Postgres 'Vanilla))

View File

@ -95,7 +95,7 @@ convOp fieldInfoMap preSetCols updPerm objs conv =
validateUpdateQueryWith validateUpdateQueryWith
:: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m) :: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m)
=> SessVarBldr ('Postgres 'Vanilla) m => SessionVariableBuilder ('Postgres 'Vanilla) m
-> ValueParser ('Postgres 'Vanilla) m S.SQLExp -> ValueParser ('Postgres 'Vanilla) m S.SQLExp
-> UpdateQuery -> UpdateQuery
-> m (AnnUpd ('Postgres 'Vanilla)) -> m (AnnUpd ('Postgres 'Vanilla))

View File

@ -20,6 +20,7 @@ module Hasura.RQL.IR.BoolExp
, STIntersectsGeomminNband(..) , STIntersectsGeomminNband(..)
, SessionArgumentPresence(..) , SessionArgumentPresence(..)
, mkSessionArgumentPresence
, ComputedFieldBoolExp(..) , ComputedFieldBoolExp(..)
, AnnComputedFieldBoolExp(..) , AnnComputedFieldBoolExp(..)
, AnnBoolExpFld(..) , AnnBoolExpFld(..)
@ -186,6 +187,7 @@ makePrisms ''GBoolExp
-- inline the session variables. -- inline the session variables.
data PartialSQLExp (b :: BackendType) data PartialSQLExp (b :: BackendType)
= PSESessVar !(SessionVarType b) !SessionVariable = PSESessVar !(SessionVarType b) !SessionVariable
| PSESession
| PSESQLExp !(SQLExpression b) | PSESQLExp !(SQLExpression b)
deriving (Generic) deriving (Generic)
deriving instance (Backend b) => Eq (PartialSQLExp b) deriving instance (Backend b) => Eq (PartialSQLExp b)
@ -196,11 +198,13 @@ instance (Backend b, Hashable (BooleanOperators b (PartialSQLExp b))) => Cachea
instance Backend b => ToJSON (PartialSQLExp b) where instance Backend b => ToJSON (PartialSQLExp b) where
toJSON = \case toJSON = \case
PSESessVar colTy sessVar -> toJSON (colTy, sessVar) PSESessVar colTy sessVar -> toJSON (colTy, sessVar)
PSESession -> String "hasura_session"
PSESQLExp e -> toJSON e PSESQLExp e -> toJSON e
isStaticValue :: PartialSQLExp backend -> Bool isStaticValue :: PartialSQLExp backend -> Bool
isStaticValue = \case isStaticValue = \case
PSESessVar _ _ -> False PSESessVar _ _ -> False
PSESession -> False
PSESQLExp _ -> True PSESQLExp _ -> True
hasStaticExp :: Backend b => OpExpG b (PartialSQLExp b) -> Bool hasStaticExp :: Backend b => OpExpG b (PartialSQLExp b) -> Bool
@ -297,8 +301,8 @@ opExpDepCol = \case
_ -> Nothing _ -> Nothing
-- | The presence of session argument in the SQL function of a computed field. -- | The presence of session argument in the SQL function of a computed field.
-- Since we only support maximum of 2 arguments in boolean expression, the position -- Since we only support computed fields with SQL functions having maximum of 2 arguments in boolean expression,
-- (if present) is either first or second. The other mandatory argument is table row input. -- the position (if present) is either first or second. The other mandatory argument is table row input.
data SessionArgumentPresence a data SessionArgumentPresence a
= SAPNotPresent = SAPNotPresent
| SAPFirst a | SAPFirst a
@ -308,6 +312,18 @@ instance (NFData a) => NFData (SessionArgumentPresence a)
instance (Cacheable a) => Cacheable (SessionArgumentPresence a) instance (Cacheable a) => Cacheable (SessionArgumentPresence a)
instance (Hashable a) => Hashable (SessionArgumentPresence a) instance (Hashable a) => Hashable (SessionArgumentPresence a)
-- | Determine the position of session argument
mkSessionArgumentPresence :: forall v a. v -> Maybe a -> FunctionTableArgument -> SessionArgumentPresence v
mkSessionArgumentPresence sessionValue = \case
Nothing -> const $ SAPNotPresent
Just _ -> \case
-- If table argument is first, then session argument will be second
FTAFirst -> SAPSecond sessionValue
-- Argument index 0 implies it is first
FTANamed _ 0 -> SAPSecond sessionValue
-- If table argument is second, then session argument will be first
FTANamed{} -> SAPFirst sessionValue
-- | This type is used to represent the kinds of boolean expression used for compouted fields -- | This type is used to represent the kinds of boolean expression used for compouted fields
-- based on the return type of the SQL function -- based on the return type of the SQL function
data ComputedFieldBoolExp (b :: BackendType) a data ComputedFieldBoolExp (b :: BackendType) a
@ -336,7 +352,7 @@ instance (Backend b, Hashable (BooleanOperators b a), Hashable a) => Hashable
data AnnComputedFieldBoolExp (b :: BackendType) a data AnnComputedFieldBoolExp (b :: BackendType) a
= AnnComputedFieldBoolExp = AnnComputedFieldBoolExp
{ _acfbXFieldInfo :: !(XComputedField b) { _acfbXFieldInfo :: !(XComputedField b)
, _acfbName :: !(ComputedFieldName) , _acfbName :: !ComputedFieldName
, _acfbFunction :: !(FunctionName b) , _acfbFunction :: !(FunctionName b)
, _acfbSessionArgumentPresence :: !(SessionArgumentPresence a) , _acfbSessionArgumentPresence :: !(SessionArgumentPresence a)
, _acfbBoolExp :: !(ComputedFieldBoolExp b a) , _acfbBoolExp :: !(ComputedFieldBoolExp b a)

View File

@ -25,12 +25,15 @@ module Hasura.RQL.Types.Column
, fromCol , fromCol
, ColumnValues , ColumnValues
, ColumnReference(..)
, columnReferenceType
) where ) where
import Hasura.Prelude import Hasura.Prelude
import qualified Data.HashMap.Strict as M import qualified Data.HashMap.Strict as M
import qualified Language.GraphQL.Draft.Syntax as G import qualified Language.GraphQL.Draft.Syntax as G
import Control.Lens.TH import Control.Lens.TH
import Data.Aeson import Data.Aeson
@ -38,9 +41,10 @@ import Data.Aeson.TH
import Data.Text.Extended import Data.Text.Extended
import Hasura.Base.Error import Hasura.Base.Error
import Hasura.Incremental (Cacheable) import Hasura.Incremental (Cacheable)
import Hasura.RQL.Types.Backend import Hasura.RQL.Types.Backend
import Hasura.RQL.Types.Common import Hasura.RQL.Types.Common
import Hasura.RQL.Types.ComputedField
import Hasura.SQL.Backend import Hasura.SQL.Backend
import Hasura.SQL.Types import Hasura.SQL.Types
@ -201,3 +205,23 @@ fromCol :: Backend b => Column b -> FieldName
fromCol = FieldName . toTxt fromCol = FieldName . toTxt
type ColumnValues b a = HashMap (Column b) a type ColumnValues b a = HashMap (Column b) a
-- | Represents a reference to a source column, possibly casted an arbitrary
-- number of times. Used within 'parseBoolExpOperations' for bookkeeping.
data ColumnReference (b :: BackendType)
= ColumnReferenceColumn !(ColumnInfo b)
| ColumnReferenceComputedField !ComputedFieldName !(ScalarType b)
| ColumnReferenceCast !(ColumnReference b) !(ColumnType b)
columnReferenceType :: ColumnReference backend -> ColumnType backend
columnReferenceType = \case
ColumnReferenceColumn column -> pgiType column
ColumnReferenceComputedField _ scalarType -> ColumnScalar scalarType
ColumnReferenceCast _ targetType -> targetType
instance Backend b => ToTxt (ColumnReference b) where
toTxt = \case
ColumnReferenceColumn column -> toTxt $ pgiColumn column
ColumnReferenceComputedField name _ -> toTxt name
ColumnReferenceCast reference targetType ->
toTxt reference <> "::" <> toTxt targetType

View File

@ -82,7 +82,7 @@ class (Backend b) => BackendMetadata (b :: BackendType) where
=> ValueParser b m v => ValueParser b m v
-> TableName b -> TableName b
-> FieldInfoMap (FieldInfo b) -> FieldInfoMap (FieldInfo b)
-> ColumnInfo b -> ColumnReference b
-> Value -> Value
-> m [OpExpG b v] -> m [OpExpG b v]

View File

@ -0,0 +1,17 @@
description: A reader fetch authors with atleast one published article
url: /v1/graphql
status: 200
headers:
X-Hasura-Role: reader
query:
query: |
query {
author{
name
}
}
response:
data:
author:
- name: Author 1
- name: Author 2

View File

@ -26,6 +26,11 @@ args:
published_on TIMESTAMP published_on TIMESTAMP
); );
CREATE FUNCTION get_articles(author_row author)
RETURNS SETOF article as $$
SELECT * FROM article WHERE author_id = author_row.id
$$ LANGUAGE SQL STABLE;
INSERT INTO article (title, content, author_id, is_published) INSERT INTO article (title, content, author_id, is_published)
VALUES VALUES
('Article 1', 'Sample article content 1', 1, false), ('Article 1', 'Sample article content 1', 1, false),
@ -126,6 +131,7 @@ args:
SELECT * FROM gpa WHERE SELECT * FROM gpa WHERE
gpa_score >= pass_gpa gpa_score >= pass_gpa
$$ language SQL STABLE; $$ language SQL STABLE;
CREATE TABLE auction ( CREATE TABLE auction (
id serial primary key, id serial primary key,
price integer not null DEFAULT 250, price integer not null DEFAULT 250,
@ -138,6 +144,28 @@ args:
(100), (120), (300), (260) (100), (120), (300), (260)
; ;
CREATE TABLE student_marks (
id SERIAL PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
physics INTEGER,
chemistry INTEGER,
mathematics INTEGER
);
INSERT INTO student_marks (name, physics, chemistry, mathematics) VALUES
('clarke', 84, 67, 70), ('george', 56, 79, 70),
('blake', 66, 89, 78), ('leonel', 90, 93, 85);
CREATE FUNCTION student_total_marks (student_row student_marks)
RETURNS INTEGER AS $$
SELECT student_row.physics + student_row.chemistry + student_row.mathematics
$$ LANGUAGE SQL STABLE;
CREATE FUNCTION student_total_marks_offset (hasura_session json, student_row student_marks)
RETURNS INTEGER AS $$
SELECT student_row.physics + student_row.chemistry + student_row.mathematics - (hasura_session ->> 'x-hasura-offset-marks')::integer
$$ LANGUAGE SQL STABLE;
- type: track_table - type: track_table
args: args:
schema: public schema: public
@ -149,6 +177,16 @@ args:
schema: public schema: public
name: article name: article
# Add computed field to author table
- type: add_computed_field
args:
table: author
name: get_articles
definition:
function: get_articles
table_argument: author_row
#Object relationship #Object relationship
- type: create_object_relationship - type: create_object_relationship
args: args:
@ -224,6 +262,18 @@ args:
_eq: true _eq: true
limit: 10 limit: 10
#Author select permission for reader
- type: create_select_permission
args:
table: author
role: reader
permission:
columns:
- name
filter:
get_articles:
is_published: true
#Author select permission for anonymous users #Author select permission for anonymous users
#Only authors with atleast one article will be shown #Only authors with atleast one article will be shown
- type: create_select_permission - type: create_select_permission
@ -495,3 +545,50 @@ args:
permission: permission:
columns: [name] columns: [name]
filter: {} filter: {}
# Track student_marks table
- type: track_table
args:
table: student_marks
- type: add_computed_field
args:
table: student_marks
name: total_marks
definition:
function: student_total_marks
table_argument: student_row
- type: add_computed_field
args:
table: student_marks
name: total_marks_offset
definition:
function: student_total_marks_offset
table_argument: student_row
session_argument: hasura_session
- type: create_select_permission
args:
table: student_marks
role: tutor
permission:
columns:
- name
filter:
total_marks:
_gte: 220
- type: create_select_permission
args:
table: student_marks
role: tutor_session
permission:
columns:
- name
computed_fields:
- total_marks
- total_marks_offset
filter:
total_marks_offset:
_gte: 220

View File

@ -3,6 +3,7 @@ args:
- type: run_sql - type: run_sql
args: args:
sql: | sql: |
DROP FUNCTION get_articles(author);
DROP TABLE article; DROP TABLE article;
DROP TABLE author; DROP TABLE author;
DROP TABLE "Track" cascade; DROP TABLE "Track" cascade;
@ -12,4 +13,7 @@ args:
DROP TABLE jsonb_table; DROP TABLE jsonb_table;
DROP TABLE gpa cascade; DROP TABLE gpa cascade;
DROP TABLE auction; DROP TABLE auction;
DROP FUNCTION student_total_marks(student_marks);
DROP FUNCTION student_total_marks_offset(json, student_marks);
DROP TABLE student_marks;
cascade: true cascade: true

View File

@ -0,0 +1,18 @@
description: Fetch student names with tutor role
url: /v1/graphql
status: 200
headers:
X-Hasura-Role: tutor
query:
query: |
query {
student_marks{
name
}
}
response:
data:
student_marks:
- name: clarke
- name: blake
- name: leonel

View File

@ -0,0 +1,24 @@
description: Fetch student names with tutor role with an offset given via hasura session
url: /v1/graphql
status: 200
headers:
X-Hasura-Role: tutor_session
X-Hasura-Offset-Marks: '10'
query:
query: |
query {
student_marks{
name
total_marks
total_marks_offset
}
}
response:
data:
student_marks:
- name: blake
total_marks: 233
total_marks_offset: 223
- name: leonel
total_marks: 268
total_marks_offset: 258

View File

@ -602,6 +602,15 @@ class TestGraphqlQueryPermissions:
def test_author_articles_without_required_headers_set(self, hge_ctx, transport): def test_author_articles_without_required_headers_set(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + '/select_articles_without_required_headers.yaml', transport) check_query_f(hge_ctx, self.dir() + '/select_articles_without_required_headers.yaml', transport)
def test_reader_author(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + '/reader_author.yaml', transport)
def test_tutor_get_students(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + '/tutor_get_students.yaml', transport)
def test_tutor_get_students_session(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + '/tutor_get_students_session.yaml', transport)
@classmethod @classmethod
def dir(cls): def dir(cls):
return 'queries/graphql_query/permissions' return 'queries/graphql_query/permissions'