mirror of
https://github.com/zhigang1992/graphql-engine.git
synced 2026-05-22 23:42:51 +08:00
This commit is contained in:
committed by
Shahidh K Muhammed
parent
3156f7d62d
commit
49dd7bf98b
@@ -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" />
|
||||
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" />
|
||||
Tweet
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
</ul>,
|
||||
]
|
||||
</div>
|
||||
</ul>,
|
||||
]
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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" />
|
||||
|
||||
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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user