graphql-engine/server/src-lib/Hasura/RQL/DDL/Metadata.hs

362 lines
11 KiB
Haskell
Raw Normal View History

2018-06-27 16:11:32 +03:00
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Hasura.RQL.DDL.Metadata
( ReplaceMetadata(..)
, TableMeta(..)
, tmObjectRelationships
, tmArrayRelationships
, tmInsertPermissions
, tmSelectPermissions
, tmUpdatePermissions
, tmDeletePermissions
, mkTableMeta
, applyQP1
, applyQP2
, DumpInternalState(..)
, ExportMetadata(..)
, fetchMetadata
, ClearMetadata(..)
, clearMetadata
) where
import Control.Lens
import Data.Aeson
import Data.Aeson.Casing
import Data.Aeson.TH
import Language.Haskell.TH.Syntax (Lift)
import qualified Data.HashMap.Strict as M
import qualified Data.HashSet as HS
import qualified Data.List as L
import qualified Data.Text as T
import Hasura.Prelude
import Hasura.RQL.DDL.Utils
import Hasura.RQL.Types
import Hasura.SQL.Types
import qualified Database.PG.Query as Q
import qualified Hasura.RQL.DDL.Permission as DP
import qualified Hasura.RQL.DDL.QueryTemplate as DQ
import qualified Hasura.RQL.DDL.Relationship as DR
import qualified Hasura.RQL.DDL.Schema.Table as DT
data TableMeta
= TableMeta
{ _tmTable :: !QualifiedTable
, _tmObjectRelationships :: ![DR.ObjRelDef]
, _tmArrayRelationships :: ![DR.ArrRelDef]
, _tmInsertPermissions :: ![DP.InsPermDef]
, _tmSelectPermissions :: ![DP.SelPermDef]
, _tmUpdatePermissions :: ![DP.UpdPermDef]
, _tmDeletePermissions :: ![DP.DelPermDef]
} deriving (Show, Eq, Lift)
mkTableMeta :: QualifiedTable -> TableMeta
mkTableMeta qt =
TableMeta qt [] [] [] [] [] []
makeLenses ''TableMeta
instance FromJSON TableMeta where
parseJSON (Object o) = do
unless (null unexpectedKeys) $
fail $ "unexpected keys when parsing TableMetadata : "
<> show (HS.toList unexpectedKeys)
TableMeta
<$> o .: tableKey
<*> o .:? orKey .!= []
<*> o .:? arKey .!= []
<*> o .:? ipKey .!= []
<*> o .:? spKey .!= []
<*> o .:? upKey .!= []
<*> o .:? dpKey .!= []
where
tableKey = "table"
orKey = "object_relationships"
arKey = "array_relationships"
ipKey = "insert_permissions"
spKey = "select_permissions"
upKey = "update_permissions"
dpKey = "delete_permissions"
unexpectedKeys =
HS.fromList (M.keys o) `HS.difference` expectedKeySet
expectedKeySet =
HS.fromList [ tableKey, orKey, arKey, ipKey
, spKey, upKey, dpKey
]
parseJSON _ =
fail "expecting an Object for TableMetadata"
$(deriveToJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''TableMeta)
data ClearMetadata
= ClearMetadata
deriving (Show, Eq, Lift)
$(deriveToJSON defaultOptions ''ClearMetadata)
instance FromJSON ClearMetadata where
parseJSON _ = return ClearMetadata
clearMetadata :: Q.TxE QErr ()
clearMetadata = Q.catchE defaultTxErrorHandler $ do
Q.unitQ "DELETE FROM hdb_catalog.hdb_query_template WHERE is_system_defined <> 'true'" () False
Q.unitQ "DELETE FROM hdb_catalog.hdb_permission WHERE is_system_defined <> 'true'" () False
Q.unitQ "DELETE FROM hdb_catalog.hdb_relationship WHERE is_system_defined <> 'true'" () False
Q.unitQ "DELETE FROM hdb_catalog.hdb_table WHERE is_system_defined <> 'true'" () False
Q.unitQ clearHdbViews () False
instance HDBQuery ClearMetadata where
type Phase1Res ClearMetadata = ()
phaseOne _ = adminOnly
phaseTwo _ _ = do
newSc <- liftTx $ clearMetadata >> DT.buildSchemaCache
writeSchemaCache newSc
return successMsg
schemaCachePolicy = SCPReload
data ReplaceMetadata
= ReplaceMetadata
{ aqTables :: ![TableMeta]
, aqQueryTemplates :: ![DQ.CreateQueryTemplate]
} deriving (Show, Eq, Lift)
$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''ReplaceMetadata)
applyQP1 :: ReplaceMetadata -> P1 ()
applyQP1 (ReplaceMetadata tables templates) = do
adminOnly
withPathK "tables" $ do
checkMultipleDecls "tables" $ map _tmTable tables
-- process each table
void $ indexedForM tables $ \table -> withTableName (table ^. tmTable) $ do
let allRels = map DR.rdName (table ^. tmObjectRelationships) <>
map DR.rdName (table ^. tmArrayRelationships)
insPerms = map DP.pdRole $ table ^. tmInsertPermissions
selPerms = map DP.pdRole $ table ^. tmSelectPermissions
updPerms = map DP.pdRole $ table ^. tmUpdatePermissions
delPerms = map DP.pdRole $ table ^. tmDeletePermissions
checkMultipleDecls "relationships" allRels
checkMultipleDecls "insert permissions" insPerms
checkMultipleDecls "select permissions" selPerms
checkMultipleDecls "update permissions" updPerms
checkMultipleDecls "delete permissions" delPerms
withPathK "queryTemplates" $
checkMultipleDecls "query templates" $ map DQ.cqtName templates
where
withTableName qt = withPathK (qualTableToTxt qt)
checkMultipleDecls t l = do
let dups = getDups l
unless (null dups) $
throw400 AlreadyExists $ "multiple declarations exist for the following " <> t <> " : "
<> T.pack (show dups)
getDups l =
l L.\\ HS.toList (HS.fromList l)
applyQP2 :: (UserInfoM m, P2C m) => ReplaceMetadata -> m RespBody
applyQP2 (ReplaceMetadata tables templates) = do
defaultSchemaCache <- liftTx $ clearMetadata >> DT.buildSchemaCache
writeSchemaCache defaultSchemaCache
withPathK "tables" $ do
-- tables and views
indexedForM_ (map _tmTable tables) $ \tableName ->
void $ DT.trackExistingTableOrViewP2 tableName
-- Relationships
indexedForM_ tables $ \table -> do
withPathK "object_relationships" $
indexedForM_ (table ^. tmObjectRelationships) $ \objRel ->
DR.objRelP2 (table ^. tmTable) objRel
withPathK "array_relationships" $
indexedForM_ (table ^. tmArrayRelationships) $ \arrRel ->
DR.arrRelP2 (table ^. tmTable) arrRel
-- Permissions
indexedForM_ tables $ \table -> do
let tableName = table ^. tmTable
tabInfo <- modifyErrAndSet500 ("apply " <> ) $ askTabInfo tableName
withPathK "insert_permissions" $ processPerms tabInfo $
table ^. tmInsertPermissions
withPathK "select_permissions" $ processPerms tabInfo $
table ^. tmSelectPermissions
withPathK "update_permissions" $ processPerms tabInfo $
table ^. tmUpdatePermissions
withPathK "delete_permissions" $ processPerms tabInfo $
table ^. tmDeletePermissions
-- query templates
withPathK "queryTemplates" $
indexedForM_ templates $ \template -> do
qti <- DQ.createQueryTemplateP1 template
void $ DQ.createQueryTemplateP2 template qti
return successMsg
where
processPerms tabInfo perms =
indexedForM_ perms $ \permDef -> do
permInfo <- DP.addPermP1 tabInfo permDef
DP.addPermP2 (tiName tabInfo) permDef permInfo
instance HDBQuery ReplaceMetadata where
type Phase1Res ReplaceMetadata = ()
phaseOne = applyQP1
phaseTwo q _ = applyQP2 q
schemaCachePolicy = SCPReload
data ExportMetadata
= ExportMetadata
deriving (Show, Eq, Lift)
instance FromJSON ExportMetadata where
parseJSON _ = return ExportMetadata
$(deriveToJSON defaultOptions ''ExportMetadata)
fetchMetadata :: Q.TxE QErr ReplaceMetadata
fetchMetadata = do
tables <- Q.catchE defaultTxErrorHandler fetchTables
let qts = map (uncurry QualifiedTable) tables
tableMetaMap = M.fromList $ zip qts $ map mkTableMeta qts
-- Fetch all the relationships
relationships <- Q.catchE defaultTxErrorHandler fetchRelationships
objRelDefs <- mkRelDefs ObjRel relationships
arrRelDefs <- mkRelDefs ArrRel relationships
-- Fetch all the permissions
permissions <- Q.catchE defaultTxErrorHandler fetchPermissions
-- Parse all the permissions
insPermDefs <- mkPermDefs PTInsert permissions
selPermDefs <- mkPermDefs PTSelect permissions
updPermDefs <- mkPermDefs PTUpdate permissions
delPermDefs <- mkPermDefs PTDelete permissions
-- Fetch all the query templates
qTmpltRows <- Q.catchE defaultTxErrorHandler fetchQTemplates
qTmpltDefs <- forM qTmpltRows $ \(qtn, Q.AltJ qtDefVal, mComment) -> do
qtDef <- decodeValue qtDefVal
return $ DQ.CreateQueryTemplate qtn qtDef mComment
let (_, postRelMap) = flip runState tableMetaMap $ do
modMetaMap tmObjectRelationships objRelDefs
modMetaMap tmArrayRelationships arrRelDefs
modMetaMap tmInsertPermissions insPermDefs
modMetaMap tmSelectPermissions selPermDefs
modMetaMap tmUpdatePermissions updPermDefs
modMetaMap tmDeletePermissions delPermDefs
return $ ReplaceMetadata (M.elems postRelMap) qTmpltDefs
where
modMetaMap l xs = do
st <- get
put $ foldr (\(qt, dfn) b -> b & at qt._Just.l %~ (:) dfn) st xs
mkPermDefs pt = mapM permRowToDef . filter (\pr -> pr ^. _4 == pt)
permRowToDef (sn, tn, rn, _, Q.AltJ pDef, mComment) = do
perm <- decodeValue pDef
return (QualifiedTable sn tn, DP.PermDef rn perm mComment)
mkRelDefs rt = mapM relRowToDef . filter (\rr -> rr ^. _4 == rt)
relRowToDef (sn, tn, rn, _, Q.AltJ rDef, mComment) = do
using <- decodeValue rDef
return (QualifiedTable sn tn, DR.RelDef rn using mComment)
fetchTables =
Q.listQ [Q.sql|
SELECT table_schema, table_name from hdb_catalog.hdb_table
WHERE is_system_defined = 'false'
|] () False
fetchRelationships =
Q.listQ [Q.sql|
SELECT table_schema, table_name, rel_name, rel_type, rel_def::json, comment
FROM hdb_catalog.hdb_relationship
WHERE is_system_defined = 'false'
|] () False
fetchPermissions =
Q.listQ [Q.sql|
SELECT table_schema, table_name, role_name, perm_type, perm_def::json, comment
FROM hdb_catalog.hdb_permission
WHERE is_system_defined = 'false'
|] () False
fetchQTemplates =
Q.listQ [Q.sql|
SELECT template_name, template_defn :: json, comment
FROM hdb_catalog.hdb_query_template
WHERE is_system_defined = 'false'
|] () False
instance HDBQuery ExportMetadata where
type Phase1Res ExportMetadata = ()
phaseOne _ = adminOnly
phaseTwo _ _ = encode <$> liftTx fetchMetadata
schemaCachePolicy = SCPNoChange
data DumpInternalState
= DumpInternalState
deriving (Show, Eq, Lift)
instance FromJSON DumpInternalState where
parseJSON _ = return DumpInternalState
$(deriveToJSON defaultOptions ''DumpInternalState)
instance HDBQuery DumpInternalState where
type Phase1Res DumpInternalState = ()
phaseOne _ = adminOnly
phaseTwo _ _ = do
sc <- askSchemaCache
return $ encode sc
schemaCachePolicy = SCPNoChange