allow mutations on views only if they are allowed by postgres (fix #232) (#339)

This commit is contained in:
Rakesh Emmadi
2018-10-12 17:36:12 +05:30
committed by Shahidh K Muhammed
parent 3156f7d62d
commit 49dd7bf98b
11 changed files with 351 additions and 120 deletions

View File

@@ -311,30 +311,30 @@ class Main extends React.Component {
</div>
{!this.state.loveConsentState.isDismissed
? [
<div
key="main_love_1"
className={styles.shareSection + ' dropdown-toggle'}
aria-expanded="false"
onClick={this.handleDropdownToggle.bind(this)}
>
<img
className={'img-responsive'}
src={pixHeart}
alt={'pix Heart'}
/>
{/* <i className={styles.heart + ' fa fa-heart'} /> */}
</div>,
<ul
key="main_love_2"
className={'dropdown-menu ' + styles.dropdown_menu}
>
<div className={styles.dropdown_menu_container}>
<div className={styles.closeDropDown}>
<i
className="fa fa-close"
onClick={this.closeLoveIcon.bind(this)}
/>
{/*
<div
key="main_love_1"
className={styles.shareSection + ' dropdown-toggle'}
aria-expanded="false"
onClick={this.handleDropdownToggle.bind(this)}
>
<img
className={'img-responsive'}
src={pixHeart}
alt={'pix Heart'}
/>
{/* <i className={styles.heart + ' fa fa-heart'} /> */}
</div>,
<ul
key="main_love_2"
className={'dropdown-menu ' + styles.dropdown_menu}
>
<div className={styles.dropdown_menu_container}>
<div className={styles.closeDropDown}>
<i
className="fa fa-close"
onClick={this.closeLoveIcon.bind(this)}
/>
{/*
<img
className={'img-responsive'}
src={closeIcon}
@@ -342,8 +342,8 @@ class Main extends React.Component {
onClick={this.closeLoveIcon.bind(this)}
/>
*/}
</div>
{/*
</div>
{/*
<div className={styles.arrow_up_dropdown} />
<div className={styles.graphqlHeartText}>
Love GraphQL Engine? Shout it from the rooftops!
@@ -354,37 +354,37 @@ class Main extends React.Component {
</span>
</div>
*/}
<div className={styles.displayFlex}>
<li className={styles.pixelText1}>
<div className={styles.displayFlex}>
<li className={styles.pixelText1}>
Roses are red, <br />
Violets are blue;
<br />
<br />
Star us on Github,
<br />
<br />
To make our <i className={'fa fa-heart'} /> go
wooooo!
</li>
<li className={'dropdown-item'}>
<a
href="https://github.com/hasura/graphql-engine"
target="_blank"
rel="noopener noreferrer"
>
<div className={styles.socialIcon}>
<img
className="img img-responsive"
src={
'https://storage.googleapis.com/hasura-graphql-engine/console/assets/githubicon.png'
}
alt={'Github'}
/>
</div>
<div className={styles.pixelText}>
<i className="fa fa-star" />
</li>
<li className={'dropdown-item'}>
<a
href="https://github.com/hasura/graphql-engine"
target="_blank"
rel="noopener noreferrer"
>
<div className={styles.socialIcon}>
<img
className="img img-responsive"
src={
'https://storage.googleapis.com/hasura-graphql-engine/console/assets/githubicon.png'
}
alt={'Github'}
/>
</div>
<div className={styles.pixelText}>
<i className="fa fa-star" />
&nbsp; Star
</div>
</a>
{/*
</div>
</a>
{/*
<div className={styles.gitHubBtn}>
<iframe
title="github"
@@ -396,32 +396,32 @@ class Main extends React.Component {
/>
</div>
*/}
</li>
<li className={'dropdown-item '}>
<a
href="https://twitter.com/intent/tweet?hashtags=graphql,postgres&text=Just%20deployed%20a%20GraphQL%20backend%20with%20@HasuraHQ!%20%E2%9D%A4%EF%B8%8F%20%F0%9F%9A%80%0Ahttps://github.com//hasura/graphql-engine%0A"
target="_blank"
rel="noopener noreferrer"
>
<div className={styles.socialIcon}>
<img
className="img img-responsive"
src={
'https://storage.googleapis.com/hasura-graphql-engine/console/assets/twittericon.png'
}
alt={'Twitter'}
/>
</div>
<div className={styles.pixelText}>
<i className="fa fa-twitter" />
</li>
<li className={'dropdown-item '}>
<a
href="https://twitter.com/intent/tweet?hashtags=graphql,postgres&text=Just%20deployed%20a%20GraphQL%20backend%20with%20@HasuraHQ!%20%E2%9D%A4%EF%B8%8F%20%F0%9F%9A%80%0Ahttps://github.com//hasura/graphql-engine%0A"
target="_blank"
rel="noopener noreferrer"
>
<div className={styles.socialIcon}>
<img
className="img img-responsive"
src={
'https://storage.googleapis.com/hasura-graphql-engine/console/assets/twittericon.png'
}
alt={'Twitter'}
/>
</div>
<div className={styles.pixelText}>
<i className="fa fa-twitter" />
&nbsp; Tweet
</div>
</a>
</li>
</div>
</div>
</a>
</li>
</div>
</ul>,
]
</div>
</ul>,
]
: null}
</div>
</div>

View File

@@ -109,6 +109,39 @@ const loadSchema = () => (dispatch, getState) => {
);
};
const fetchViewInfoFromInformationSchema = (schemaName, viewName) => (
dispatch,
getState
) => {
const url = Endpoints.getSchema;
const options = {
credentials: globalCookiePolicy,
method: 'POST',
headers: dataHeaders(getState),
body: JSON.stringify({
type: 'select',
args: {
table: {
name: 'views',
schema: 'information_schema',
},
columns: [
'is_updatable',
'is_insertable_into',
'is_trigger_updatable',
'is_trigger_deletable',
'is_trigger_insertable_into',
],
where: {
table_name: viewName,
table_schema: schemaName,
},
},
}),
};
return dispatch(requestAction(url, options));
};
const loadUntrackedSchema = () => (dispatch, getState) => {
const url = Endpoints.getSchema;
const currentSchema = getState().tables.currentSchema;
@@ -485,4 +518,5 @@ export {
UPDATE_REMOTE_SCHEMA_MANUAL_REL,
fetchTableListBySchema,
RESET_MANUAL_REL_TABLE_LIST,
fetchViewInfoFromInformationSchema,
};

View File

@@ -31,15 +31,41 @@ import {
import PermissionBuilder from './PermissionBuilder/PermissionBuilder';
import TableHeader from '../TableCommon/TableHeader';
import ViewHeader from '../TableBrowseRows/ViewHeader';
import { setTable } from '../DataActions';
import { setTable, fetchViewInfoFromInformationSchema } from '../DataActions';
import { getIngForm, escapeRegExp } from '../utils';
import { legacyOperatorsMap } from './PermissionBuilder/utils';
class Permissions extends Component {
constructor() {
super();
this.state = {};
this.state.viewInfo = {};
}
componentDidMount() {
this.props.dispatch({ type: RESET });
const currentSchema = this.props.allSchemas.find(
t => t.table_name === this.props.tableName
);
if (!currentSchema) {
alert('Invalid schema');
return;
}
this.props.dispatch(setTable(this.props.tableName));
this.props
.dispatch(
fetchViewInfoFromInformationSchema(
currentSchema.table_schema,
this.props.tableName
)
)
.then(r => {
if (r.length > 0) {
this.setState({ ...this.state, viewInfo: r[0] });
}
});
}
render() {
@@ -62,7 +88,26 @@ class Permissions extends Component {
if (tableType === 'table') {
qTypes = ['insert', 'select', 'update', 'delete'];
} else if (tableType === 'view') {
qTypes = ['select'];
qTypes = [];
// Add insert/update permission if it is insertable/updatable as returned by pg
if (
this.state.viewInfo &&
'is_insertable_into' in this.state.viewInfo &&
this.state.viewInfo.is_insertable_into === 'YES'
) {
qTypes.push('insert');
}
qTypes.push('select');
if (
this.state.viewInfo &&
'is_updatable' in this.state.viewInfo &&
this.state.viewInfo.is_updatable === 'YES'
) {
qTypes.push('update');
qTypes.push('delete');
}
}
const tSchema = allSchemas.find(t => t.table_name === tableName);
@@ -379,6 +424,21 @@ class Permissions extends Component {
</div>
);
const getViewPermissionNote = () => {
let showNote = false;
if (!(this.state.viewInfo && 'is_insertable_into' in this.state.viewInfo && this.state.viewInfo.is_insertable_into === 'YES') && !(this.state.viewInfo && 'is_updatable' in this.state.viewInfo && this.state.viewInfo.is_updatable === 'YES')) {
showNote = true;
}
return showNote ? (
<div className={styles.permissionsLegend}>
<i className="fa fa-question-circle" aria-hidden="true" />
&nbsp;
You cannot insert/update into this view
</div>
) : '';
};
const getPermissionsTable = (tableSchema, queryTypes, permsState) => {
const permissionsSymbols = {
// <i className="fa fa-star" aria-hidden="true"/>
@@ -394,6 +454,7 @@ class Permissions extends Component {
return (
<div>
{getPermissionsLegend(permissionsSymbols)}
{getViewPermissionNote()}
<table className={`table table-bordered ${styles.permissionsTable}`}>
{getPermissionsTableHead(queryTypes)}
{getPermissionsTableBody(

View File

@@ -124,6 +124,12 @@ isValidField = \case
isRelEligible rn rt = isValidName (G.Name $ getRelTxt rn)
&& isValidTableName rt
upsertable :: [TableConstraint] -> Bool -> Bool -> Bool
upsertable constraints isUpsertAllowed view =
not (null uniqueOrPrimaryCons) && isUpsertAllowed && not view
where
uniqueOrPrimaryCons = filter isUniqueOrPrimary constraints
toValidFieldInfos :: FieldInfoMap -> [FieldInfo]
toValidFieldInfos = filter isValidField . Map.elems
@@ -144,12 +150,6 @@ isRelNullable fim ri = isNullable
lColInfos = getColInfos lCols allCols
isNullable = any pgiIsNullable lColInfos
isUpsertAllowed :: [TableConstraint] -> Bool -> Bool
isUpsertAllowed constraints upsertPerm =
not (null uniqueOrPrimaryCons) && upsertPerm
where
uniqueOrPrimaryCons = filter isUniqueOrPrimary constraints
mkColName :: PGCol -> G.Name
mkColName (PGCol n) = G.Name n
@@ -828,7 +828,7 @@ insert_table(
mkInsMutFld
:: QualifiedTable -> Bool -> ObjFldInfo
mkInsMutFld tn upsertAllowed =
mkInsMutFld tn isUpsertable =
ObjFldInfo (Just desc) fldName (fromInpValL inputVals) $
G.toGT $ mkMutRespTy tn
where
@@ -843,8 +843,7 @@ mkInsMutFld tn upsertAllowed =
InpValInfo (Just objsArgDesc) "objects" $ G.toGT $
G.toNT $ G.toLT $ G.toNT $ mkInsInpTy tn
onConflictInpVal = bool Nothing (Just onConflictArg)
upsertAllowed
onConflictInpVal = bool Nothing (Just onConflictArg) isUpsertable
onConflictDesc = "on conflict condition"
onConflictArg =
@@ -954,7 +953,8 @@ instance Monoid RootFlds where
mkOnConflictTypes
:: QualifiedTable -> [TableConstraint] -> [PGCol] -> Bool -> [TypeInfo]
mkOnConflictTypes tn c cols = bool [] tyInfos
mkOnConflictTypes tn c cols =
bool [] tyInfos
where
tyInfos = [ TIEnum mkConflictActionTy
, TIEnum $ mkConstriantTy tn constraints
@@ -977,31 +977,40 @@ mkGCtxRole'
-> [PGColInfo]
-- constraints
-> [TableConstraint]
-> Maybe ViewInfo
-- all columns
-> [PGCol]
-> TyAgg
mkGCtxRole' tn insPermM selFldsM updColsM delPermM pkeyCols constraints allCols =
mkGCtxRole' tn insPermM selFldsM updColsM delPermM pkeyCols constraints viM allCols =
TyAgg (mkTyInfoMap allTypes) fieldMap ordByEnums
where
upsertPerm = or $ fmap snd insPermM
upsertAllowed = isUpsertAllowed constraints upsertPerm
ordByEnums = fromMaybe Map.empty ordByResCtxM
onConflictTypes = mkOnConflictTypes tn constraints allCols upsertAllowed
upsertPerm = or $ fmap snd insPermM
isUpsertable = upsertable constraints upsertPerm $ isJust viM
onConflictTypes = mkOnConflictTypes tn constraints allCols isUpsertable
jsonOpTys = fromMaybe [] updJSONOpInpObjTysM
relInsInpObjTys = map TIInpObj relInsInpObjs
relInsInpObjTys = maybe [] (map TIInpObj) $
mutHelper viIsInsertable relInsInpObjsM
allTypes = relInsInpObjTys <> onConflictTypes <> jsonOpTys <> catMaybes
[ TIInpObj <$> insInpObjM
, TIInpObj <$> updSetInpObjM
, TIInpObj <$> updIncInpObjM
, TIInpObj <$> boolExpInpObjM
, TIObj <$> mutRespObjM
allTypes = relInsInpObjTys <> onConflictTypes <> jsonOpTys
<> queryTypes <> mutationTypes
queryTypes = catMaybes
[ TIInpObj <$> boolExpInpObjM
, TIObj <$> selObjM
, TIEnum <$> ordByTyInfoM
]
mutationTypes = catMaybes
[ TIInpObj <$> mutHelper viIsInsertable insInpObjM
, TIInpObj <$> mutHelper viIsUpdatable updSetInpObjM
, TIInpObj <$> mutHelper viIsUpdatable updIncInpObjM
, TIObj <$> mutRespObjM
]
mutHelper f objM = bool Nothing objM $ isMutable f viM
fieldMap = Map.unions $ catMaybes
[ insInpObjFldsM, updSetInpObjFldsM, boolExpInpObjFldsM
, selObjFldsM, Just selByPKeyObjFlds
@@ -1021,7 +1030,7 @@ mkGCtxRole' tn insPermM selFldsM updColsM delPermM pkeyCols constraints allCols
-- column fields used in insert input object
insInpObjFldsM = mkColFldMap (mkInsInpTy tn) <$> insColsM
-- relationship input objects
relInsInpObjs = maybe [] (const $ mkRelInsInps tn upsertAllowed) insCtxM
relInsInpObjsM = const (mkRelInsInps tn isUpsertable) <$> insCtxM
-- update set input type
updSetInpObjM = mkUpdSetInp tn <$> updColsM
-- update increment input type
@@ -1049,10 +1058,13 @@ mkGCtxRole' tn insPermM selFldsM updColsM delPermM pkeyCols constraints allCols
-- mut resp obj
mutRespObjM =
if isJust insCtxM || isJust updColsM || isJust delPermM
if isMut
then Just $ mkMutRespObj tn $ isJust selFldsM
else Nothing
isMut = (isJust insColsM || isJust updColsM || isJust delPermM)
&& any (`isMutable` viM) [viIsInsertable, viIsUpdatable, viIsDeletable]
-- table obj
selObjM = mkTableObj tn <$> selFldsM
-- the fields used in table object
@@ -1076,20 +1088,25 @@ getRootFldsRole'
-> Maybe (S.BoolExp, Maybe Int, [T.Text]) -- select filter
-> Maybe ([PGCol], S.BoolExp, [T.Text]) -- update filter
-> Maybe (S.BoolExp, [T.Text]) -- delete filter
-> Maybe ViewInfo
-> RootFlds
getRootFldsRole' tn primCols constraints fields insM selM updM delM =
getRootFldsRole' tn primCols constraints fields insM selM updM delM viM =
RootFlds mFlds
where
mFlds = mapFromL (either _fiName _fiName . snd) $ catMaybes
[ getInsDet <$> insM, getSelDet <$> selM
, getUpdDet <$> updM, getDelDet <$> delM
[ mutHelper viIsInsertable getInsDet insM
, mutHelper viIsUpdatable getUpdDet updM
, mutHelper viIsDeletable getDelDet delM
, getSelDet <$> selM
, getPKeySelDet selM $ getColInfos primCols colInfos
]
mutHelper f getDet mutM =
bool Nothing (getDet <$> mutM) $ isMutable f viM
colInfos = fst $ validPartitionFieldInfoMap fields
getInsDet (hdrs, upsertPerm) =
let upsertAllowed = isUpsertAllowed constraints upsertPerm
let isUpsertable = upsertable constraints upsertPerm $ isJust viM
in ( OCInsert tn hdrs
, Right $ mkInsMutFld tn upsertAllowed
, Right $ mkInsMutFld tn isUpsertable
)
getUpdDet (updCols, updFltr, hdrs) =
( OCUpdate tn updFltr hdrs
@@ -1176,18 +1193,19 @@ mkGCtxRole
-> FieldInfoMap
-> [PGCol]
-> [TableConstraint]
-> Maybe ViewInfo
-> RoleName
-> RolePermInfo
-> m (TyAgg, RootFlds, InsCtxMap)
mkGCtxRole tableCache tn fields pCols constraints role permInfo = do
mkGCtxRole tableCache tn fields pCols constraints viM role permInfo = do
selFldsM <- mapM (getSelFlds tableCache fields role) $ _permSel permInfo
tabInsCtxM <- forM (_permIns permInfo) $ \ipi -> do
tic <- mkInsCtx role tableCache fields ipi
return (tic, ipiAllowUpsert ipi)
let updColsM = filterColInfos . upiCols <$> _permUpd permInfo
tyAgg = mkGCtxRole' tn tabInsCtxM selFldsM updColsM
(void $ _permDel permInfo) pColInfos constraints allCols
rootFlds = getRootFldsRole tn pCols constraints fields permInfo
(void $ _permDel permInfo) pColInfos constraints viM allCols
rootFlds = getRootFldsRole tn pCols constraints fields viM permInfo
insCtxMap = maybe Map.empty (Map.singleton tn) $ fmap fst tabInsCtxM
return (tyAgg, rootFlds, insCtxMap)
where
@@ -1202,12 +1220,14 @@ getRootFldsRole
-> [PGCol]
-> [TableConstraint]
-> FieldInfoMap
-> Maybe ViewInfo
-> RolePermInfo
-> RootFlds
getRootFldsRole tn pCols constraints fields (RolePermInfo insM selM updM delM) =
getRootFldsRole tn pCols constraints fields viM (RolePermInfo insM selM updM delM) =
getRootFldsRole' tn pCols constraints fields
(mkIns <$> insM) (mkSel <$> selM)
(mkUpd <$> updM) (mkDel <$> delM)
viM
where
mkIns i = (ipiRequiredHeaders i, ipiAllowUpsert i)
mkSel s = (spiFilter s, spiLimit s, spiRequiredHeaders s)
@@ -1222,12 +1242,13 @@ mkGCtxMapTable
=> TableCache
-> TableInfo
-> m (Map.HashMap RoleName (TyAgg, RootFlds, InsCtxMap))
mkGCtxMapTable tableCache (TableInfo tn _ fields rolePerms constraints pkeyCols _) = do
m <- Map.traverseWithKey (mkGCtxRole tableCache tn fields pkeyCols validConstraints) rolePerms
mkGCtxMapTable tableCache (TableInfo tn _ fields rolePerms constraints pkeyCols viewInfo _) = do
m <- Map.traverseWithKey
(mkGCtxRole tableCache tn fields pkeyCols validConstraints viewInfo) rolePerms
let adminInsCtx = mkAdminInsCtx tn fields
adminCtx = mkGCtxRole' tn (Just (adminInsCtx, True))
(Just selFlds) (Just colInfos) (Just ())
pkeyColInfos validConstraints allCols
pkeyColInfos validConstraints viewInfo allCols
adminInsCtxMap = Map.singleton tn adminInsCtx
return $ Map.insert adminRole (adminCtx, adminRootFlds, adminInsCtxMap) m
where
@@ -1240,9 +1261,10 @@ mkGCtxMapTable tableCache (TableInfo tn _ fields rolePerms constraints pkeyCols
FIRelationship relInfo -> Right (relInfo, noFilter, Nothing, isRelNullable fields relInfo)
noFilter = S.BELit True
adminRootFlds =
getRootFldsRole' tn pkeyCols constraints fields
getRootFldsRole' tn pkeyCols validConstraints fields
(Just ([], True)) (Just (noFilter, Nothing, []))
(Just (allCols, noFilter, [])) (Just (noFilter, []))
viewInfo
mkScalarTyInfo :: PGColType -> ScalarTyInfo
mkScalarTyInfo = ScalarTyInfo Nothing

View File

@@ -290,6 +290,19 @@ class (ToJSON a) => IsPerm a where
:: DropPerm a -> PermAccessor (PermInfo a)
getPermAcc2 _ = permAccessor
validateViewPerm
:: (IsPerm a, QErrM m) => PermDef a -> TableInfo -> m ()
validateViewPerm permDef tableInfo =
case permAcc of
PASelect -> return ()
PAInsert -> mutableView tn viIsInsertable viewInfo "insertable"
PAUpdate -> mutableView tn viIsUpdatable viewInfo "updatable"
PADelete -> mutableView tn viIsDeletable viewInfo "deletable"
where
tn = tiName tableInfo
viewInfo = tiViewInfo tableInfo
permAcc = getPermAcc1 permDef
addPermP1 :: (QErrM m, CacheRM m, IsPerm a) => TableInfo -> PermDef a -> m (PermInfo a)
addPermP1 tabInfo pd = do
assertPermNotDefined (pdRole pd) (getPermAcc1 pd) tabInfo
@@ -311,6 +324,7 @@ instance (IsPerm a) => HDBQuery (CreatePerm a) where
phaseOne (WithTable tn pd) = do
tabInfo <- createPermP1 tn
validateViewPerm pd tabInfo
addPermP1 tabInfo pd
phaseTwo (WithTable tn pd) permInfo = do

View File

@@ -48,6 +48,35 @@ saveTableToCatalog (QualifiedTable sn tn) =
INSERT INTO "hdb_catalog"."hdb_table" VALUES ($1, $2)
|] (sn, tn) False
getViewInfo :: QualifiedTable -> Q.TxE QErr (Maybe ViewInfo)
getViewInfo (QualifiedTable sn tn) = do
tableTy <- runIdentity . Q.getRow <$> Q.withQE defaultTxErrorHandler
[Q.sql|
SELECT table_type FROM information_schema.tables
WHERE table_schema = $1
AND table_name = $2
|] (sn, tn) False
bool (return Nothing) buildViewInfo $ isView tableTy
where
buildViewInfo = do
(is_upd, is_ins, is_trig_upd, is_trig_del, is_trig_ins)
<- Q.getRow <$> Q.withQE defaultTxErrorHandler
[Q.sql|
SELECT is_updatable :: boolean,
is_insertable_into::boolean,
is_trigger_updatable::boolean,
is_trigger_deletable::boolean,
is_trigger_insertable_into::boolean
FROM information_schema.views
WHERE table_schema = $1
AND table_name = $2
|] (sn, tn) False
return $ Just $ ViewInfo
(is_upd || is_trig_upd)
(is_upd || is_trig_del)
(is_ins || is_trig_ins)
-- Build the TableInfo with all its columns
getTableInfo :: QualifiedTable -> Bool -> Q.TxE QErr TableInfo
getTableInfo qt@(QualifiedTable sn tn) isSystemDefined = do
@@ -61,6 +90,9 @@ getTableInfo qt@(QualifiedTable sn tn) isSystemDefined = do
unless (tableExists == [Identity True]) $
throw400 NotExists $ "no such table/view exists in postgres : " <>> qt
-- Fetch View information
viewInfo <- getViewInfo qt
-- Fetch the column details
colData <- Q.catchE defaultTxErrorHandler $ Q.listQ [Q.sql|
SELECT column_name, to_json(udt_name), is_nullable::boolean
@@ -87,7 +119,7 @@ getTableInfo qt@(QualifiedTable sn tn) isSystemDefined = do
|] (sn, tn) False
let colDetails = flip map colData $ \(colName, Q.AltJ colTy, isNull)
-> (colName, colTy, isNull)
return $ mkTableInfo qt isSystemDefined rawConstraints colDetails pkeyCols
return $ mkTableInfo qt isSystemDefined rawConstraints colDetails pkeyCols viewInfo
where
mkPKeyCols [] = return []
mkPKeyCols [Identity (Q.AltJ pkeyCols)] = return pkeyCols

View File

@@ -52,6 +52,10 @@ convDeleteQuery
convDeleteQuery prepValBuilder (DeleteQuery tableName rqlBE mRetCols) = do
tableInfo <- askTabInfo tableName
-- If table is view then check if it deletable
mutableView tableName viIsDeletable
(tiViewInfo tableInfo) "deletable"
-- Check if the role has delete permissions
delPerm <- askDelPermInfo tableInfo

View File

@@ -155,6 +155,10 @@ convInsertQuery objsParser prepFn (InsertQuery tableName val oC mRetCols) = do
-- Get the current table information
tableInfo <- askTabInfo tableName
-- If table is view then check if it is insertable
mutableView tableName viIsInsertable
(tiViewInfo tableInfo) "insertable"
-- Check if the role has insert permissions
insPerm <- askInsPermInfo tableInfo

View File

@@ -113,6 +113,10 @@ convUpdateQuery f uq = do
let tableName = uqTable uq
tableInfo <- withPathK "table" $ askTabInfo tableName
-- If it is view then check if it is updatable
mutableView tableName viIsUpdatable
(tiViewInfo tableInfo) "updatable"
-- Check if the role has update permissions
updPerm <- askUpdPermInfo tableInfo

View File

@@ -14,6 +14,9 @@ module Hasura.RQL.Types.SchemaCache
, TableInfo(..)
, TableConstraint(..)
, ConstraintType(..)
, ViewInfo(..)
, isMutable
, mutableView
, onlyIntCols
, onlyJSONBCols
, isUniqueOrPrimary
@@ -412,6 +415,26 @@ isUniqueOrPrimary (TableConstraint ty _) = case ty of
CTPRIMARYKEY -> True
CTUNIQUE -> True
data ViewInfo
= ViewInfo
{ viIsUpdatable :: !Bool
, viIsDeletable :: !Bool
, viIsInsertable :: !Bool
} deriving (Show, Eq)
$(deriveToJSON (aesonDrop 2 snakeCase) ''ViewInfo)
isMutable :: (ViewInfo -> Bool) -> Maybe ViewInfo -> Bool
isMutable _ Nothing = True
isMutable f (Just vi) = f vi
mutableView :: (MonadError QErr m) => QualifiedTable
-> (ViewInfo -> Bool) -> Maybe ViewInfo
-> T.Text -> m ()
mutableView qt f mVI operation =
unless (isMutable f mVI) $ throw400 NotSupported $
"view " <> qt <<> " is not " <> operation
data TableInfo
= TableInfo
{ tiName :: !QualifiedTable
@@ -420,15 +443,17 @@ data TableInfo
, tiRolePermInfoMap :: !RolePermInfoMap
, tiConstraints :: ![TableConstraint]
, tiPrimaryKeyCols :: ![PGCol]
, tiViewInfo :: !(Maybe ViewInfo)
, tiEventTriggerInfoMap :: !EventTriggerInfoMap
} deriving (Show, Eq)
$(deriveToJSON (aesonDrop 2 snakeCase) ''TableInfo)
mkTableInfo :: QualifiedTable -> Bool -> [(ConstraintType, ConstraintName)]
-> [(PGCol, PGColType, Bool)] -> [PGCol] -> TableInfo
mkTableInfo tn isSystemDefined rawCons cols pcols =
TableInfo tn isSystemDefined colMap (M.fromList []) constraints pcols (M.fromList [])
-> [(PGCol, PGColType, Bool)] -> [PGCol]
-> Maybe ViewInfo -> TableInfo
mkTableInfo tn isSystemDefined rawCons cols pcols mVI =
TableInfo tn isSystemDefined colMap (M.fromList []) constraints pcols mVI (M.fromList [])
where
constraints = flip map rawCons $ uncurry TableConstraint
colMap = M.fromList $ map f cols
@@ -563,6 +588,7 @@ delFldFromCache fn =
Just _ -> return $
ti { tiFieldInfoMap = M.delete fn fim }
Nothing -> throw500 "field does not exist"
data PermAccessor a where
PAInsert :: PermAccessor InsPermInfo
PASelect :: PermAccessor SelPermInfo
@@ -726,13 +752,13 @@ getDependentObjsOfTableWith f objId ti =
getDependentRelsOfTable :: (T.Text -> Bool) -> SchemaObjId
-> TableInfo -> [SchemaObjId]
getDependentRelsOfTable rsnFn objId (TableInfo tn _ fim _ _ _ _) =
getDependentRelsOfTable rsnFn objId (TableInfo tn _ fim _ _ _ _ _) =
map (SOTableObj tn . TORel . riName) $
filter (isDependentOn rsnFn objId) $ getRels fim
getDependentPermsOfTable :: (T.Text -> Bool) -> SchemaObjId
-> TableInfo -> [SchemaObjId]
getDependentPermsOfTable rsnFn objId (TableInfo tn _ _ rpim _ _ _) =
getDependentPermsOfTable rsnFn objId (TableInfo tn _ _ rpim _ _ _ _) =
concat $ flip M.mapWithKey rpim $
\rn rpi -> map (SOTableObj tn . TOPerm rn) $ getDependentPerms' rsnFn objId rpi
@@ -751,7 +777,7 @@ getDependentPerms' rsnFn objId (RolePermInfo mipi mspi mupi mdpi) =
getDependentTriggersOfTable :: (T.Text -> Bool) -> SchemaObjId
-> TableInfo -> [SchemaObjId]
getDependentTriggersOfTable rsnFn objId (TableInfo tn _ _ _ _ _ et) =
getDependentTriggersOfTable rsnFn objId (TableInfo tn _ _ _ _ _ _ et) =
map (SOTableObj tn . TOTrigger . otiTriggerName ) $ filter (isDependentOn rsnFn objId) $ getTriggers et
getOpInfo :: TriggerName -> TableInfo -> Maybe SubscribeOpSpec -> Maybe OpTriggerInfo

View File

@@ -1,6 +1,7 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Hasura.SQL.Types where
@@ -21,6 +22,7 @@ import qualified Data.ByteString.Lazy as BL
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Extended as T
import qualified Database.PostgreSQL.LibPQ as PQ
import qualified PostgreSQL.Binary.Decoding as PD
class ToSQL a where
toSQL :: a -> BB.Builder
@@ -106,6 +108,34 @@ instance DQuote TableName where
instance ToSQL TableName where
toSQL = toSQL . toIden
data TableType
= TTBaseTable
| TTView
| TTForeignTable
| TTLocalTemporary
deriving (Eq)
tableTyToTxt :: TableType -> T.Text
tableTyToTxt TTBaseTable = "BASE TABLE"
tableTyToTxt TTView = "VIEW"
tableTyToTxt TTForeignTable = "FOREIGN TABLE"
tableTyToTxt TTLocalTemporary = "LOCAL TEMPORARY"
instance Show TableType where
show = T.unpack . tableTyToTxt
instance Q.FromCol TableType where
fromCol bs = flip Q.fromColHelper bs $ PD.enum $ \case
"BASE TABLE" -> Just TTBaseTable
"VIEW" -> Just TTView
"FOREIGN TABLE" -> Just TTForeignTable
"LOCAL TEMPORARY" -> Just TTLocalTemporary
_ -> Nothing
isView :: TableType -> Bool
isView TTView = True
isView _ = False
newtype ConstraintName
= ConstraintName { getConstraintTxt :: T.Text }
deriving (Show, Eq, FromJSON, ToJSON, Q.ToPrepArg, Q.FromCol, Hashable, Lift)