diff --git a/console/src/components/Main/Main.js b/console/src/components/Main/Main.js
index c9774d6e..d12acb3f 100644
--- a/console/src/components/Main/Main.js
+++ b/console/src/components/Main/Main.js
@@ -311,30 +311,30 @@ class Main extends React.Component {
{!this.state.loveConsentState.isDismissed
? [
-
-

- {/*
*/}
-
,
-
-
-
-
- {/*
+
+

+ {/*
*/}
+
,
+
+
+
+
+ {/*

*/}
-
- {/*
+
+ {/*
Love GraphQL Engine? Shout it from the rooftops!
@@ -354,37 +354,37 @@ class Main extends React.Component {
*/}
-
-
-
+
+
-
Roses are red,
Violets are blue;
-
+
Star us on Github,
-
+
To make our go
wooooo!
-
-
-
-
-
-

-
-
+
+ {/*
*/}
-
-
-
-
-
-

-
-
+
+
+
-
,
- ]
+
+ ,
+ ]
: null}
diff --git a/console/src/components/Services/Data/DataActions.js b/console/src/components/Services/Data/DataActions.js
index ed15ebeb..b57ff690 100644
--- a/console/src/components/Services/Data/DataActions.js
+++ b/console/src/components/Services/Data/DataActions.js
@@ -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,
};
diff --git a/console/src/components/Services/Data/TablePermissions/Permissions.js b/console/src/components/Services/Data/TablePermissions/Permissions.js
index 3c500188..3d35239d 100644
--- a/console/src/components/Services/Data/TablePermissions/Permissions.js
+++ b/console/src/components/Services/Data/TablePermissions/Permissions.js
@@ -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 {
);
+ 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 ? (
+
+
+
+ You cannot insert/update into this view
+
+ ) : '';
+ };
+
const getPermissionsTable = (tableSchema, queryTypes, permsState) => {
const permissionsSymbols = {
//
@@ -394,6 +454,7 @@ class Permissions extends Component {
return (
{getPermissionsLegend(permissionsSymbols)}
+ {getViewPermissionNote()}
{getPermissionsTableHead(queryTypes)}
{getPermissionsTableBody(
diff --git a/server/src-lib/Hasura/GraphQL/Schema.hs b/server/src-lib/Hasura/GraphQL/Schema.hs
index 708131f9..bcc04d8a 100644
--- a/server/src-lib/Hasura/GraphQL/Schema.hs
+++ b/server/src-lib/Hasura/GraphQL/Schema.hs
@@ -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
diff --git a/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs b/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs
index 35b791f7..8b60d38a 100644
--- a/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs
+++ b/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs
@@ -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
diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs
index ad7544c2..0aed723f 100644
--- a/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs
+++ b/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs
@@ -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
diff --git a/server/src-lib/Hasura/RQL/DML/Delete.hs b/server/src-lib/Hasura/RQL/DML/Delete.hs
index 94230074..d2c46e4a 100644
--- a/server/src-lib/Hasura/RQL/DML/Delete.hs
+++ b/server/src-lib/Hasura/RQL/DML/Delete.hs
@@ -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
diff --git a/server/src-lib/Hasura/RQL/DML/Insert.hs b/server/src-lib/Hasura/RQL/DML/Insert.hs
index 50b1b23e..e6121072 100644
--- a/server/src-lib/Hasura/RQL/DML/Insert.hs
+++ b/server/src-lib/Hasura/RQL/DML/Insert.hs
@@ -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
diff --git a/server/src-lib/Hasura/RQL/DML/Update.hs b/server/src-lib/Hasura/RQL/DML/Update.hs
index fbf894d4..3574cb6f 100644
--- a/server/src-lib/Hasura/RQL/DML/Update.hs
+++ b/server/src-lib/Hasura/RQL/DML/Update.hs
@@ -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
diff --git a/server/src-lib/Hasura/RQL/Types/SchemaCache.hs b/server/src-lib/Hasura/RQL/Types/SchemaCache.hs
index 88152695..b9efe6ca 100644
--- a/server/src-lib/Hasura/RQL/Types/SchemaCache.hs
+++ b/server/src-lib/Hasura/RQL/Types/SchemaCache.hs
@@ -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
diff --git a/server/src-lib/Hasura/SQL/Types.hs b/server/src-lib/Hasura/SQL/Types.hs
index 4659431d..9f873466 100644
--- a/server/src-lib/Hasura/SQL/Types.hs
+++ b/server/src-lib/Hasura/SQL/Types.hs
@@ -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)