mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-16 01:44:03 +03:00
0aaf006c25
### Description This PR is the first of several PRs meant to introduce Generalized Joins. In this first PR, we add non-breaking changes to the Metadata types for DB-to-DB remote joins. Note that we are currently rejecting the new remote join format in order to keep folks from breaking their metadata (in case of a downgrade). These issues will be tackled (and JSON changes reverted) in subsequent PRs. This PR also changes the way we construct the schema cache, and breaks the way we process sources in two steps: we first resolve each source and construct a cache of their tables' raw info, then in a second step we build the source output. This is so that we have access to the target source's tables when building db-to-db relationships. ### Notes - this PR contains a few minor cleanups of the schema - it also fixes a bug in how we do renames in remote schema relationships - it introduces cross-source schema dependencies https://github.com/hasura/graphql-engine-mono/pull/1727 Co-authored-by: Evie Ciobanu <1017953+eviefp@users.noreply.github.com> GitOrigin-RevId: f625473077bc5fff5d941b70e9a116192bc1eb22
186 lines
7.1 KiB
Haskell
186 lines
7.1 KiB
Haskell
-- | Helper functions for generating the schema of database tables
|
|
module Hasura.GraphQL.Schema.Table
|
|
( getTableGQLName
|
|
, tableSelectColumnsEnum
|
|
, tableUpdateColumnsEnum
|
|
, tablePermissions
|
|
, tableSelectPermissions
|
|
, tableSelectFields
|
|
, tableColumns
|
|
, tableSelectColumns
|
|
, tableUpdateColumns
|
|
) where
|
|
|
|
import Hasura.Prelude
|
|
|
|
import qualified Data.HashMap.Strict as Map
|
|
import qualified Data.HashSet as Set
|
|
import qualified Language.GraphQL.Draft.Syntax as G
|
|
|
|
import Data.Text.Extended
|
|
|
|
import qualified Hasura.GraphQL.Parser as P
|
|
|
|
import Hasura.Base.Error (QErr)
|
|
import Hasura.GraphQL.Parser (Kind (..), Parser)
|
|
import Hasura.GraphQL.Parser.Class
|
|
import Hasura.GraphQL.Schema.Backend
|
|
import Hasura.RQL.DML.Internal (getRolePermInfo)
|
|
import Hasura.RQL.Types
|
|
|
|
-- | Helper function to get the table GraphQL name. A table may have a
|
|
-- custom name configured with it. When the custom name exists, the GraphQL nodes
|
|
-- that are generated according to the custom name. For example: Let's say,
|
|
-- we have a table called `users address`, the name of the table is not GraphQL
|
|
-- compliant so we configure the table with a GraphQL compliant name,
|
|
-- say `users_address`
|
|
-- The generated top-level nodes of this table will be like `users_address`,
|
|
-- `insert_users_address` etc
|
|
getTableGQLName
|
|
:: forall b m. (Backend b, MonadError QErr m)
|
|
=> TableInfo b -> m G.Name
|
|
getTableGQLName tableInfo = do
|
|
let coreInfo = _tiCoreInfo tableInfo
|
|
tableName = _tciName coreInfo
|
|
tableCustomName = _tcCustomName $ _tciCustomConfig coreInfo
|
|
tableCustomName
|
|
`onNothing` tableGraphQLName @b tableName
|
|
`onLeft` throwError
|
|
|
|
-- | Table select columns enum
|
|
--
|
|
-- Parser for an enum type that matches the columns of the given
|
|
-- table. Used as a parameter for "distinct", among others. Maps to
|
|
-- the table_select_column object.
|
|
--
|
|
-- Return Nothing if there's no column the current user has "select"
|
|
-- permissions for.
|
|
tableSelectColumnsEnum
|
|
:: forall m n r b
|
|
. (BackendSchema b, MonadSchema n m, MonadRole r m, MonadTableInfo r m)
|
|
=> SourceName
|
|
-> TableInfo b
|
|
-> SelPermInfo b
|
|
-> m (Maybe (Parser 'Both n (Column b)))
|
|
tableSelectColumnsEnum sourceName tableInfo selectPermissions = do
|
|
tableGQLName <- getTableGQLName @b tableInfo
|
|
columns <- tableSelectColumns sourceName tableInfo selectPermissions
|
|
let enumName = tableGQLName <> $$(G.litName "_select_column")
|
|
description = Just $ G.Description $
|
|
"select columns of table " <>> tableInfoName tableInfo
|
|
pure $ P.enum enumName description <$> nonEmpty
|
|
[ ( define $ pgiName column
|
|
, pgiColumn column
|
|
)
|
|
| column <- columns
|
|
]
|
|
where
|
|
define name =
|
|
P.mkDefinition name (Just $ G.Description "column name") P.EnumValueInfo
|
|
|
|
-- | Table update columns enum
|
|
--
|
|
-- Parser for an enum type that matches the columns of the given
|
|
-- table. Used for conflict resolution in "insert" mutations, among
|
|
-- others. Maps to the table_update_column object.
|
|
--
|
|
-- If there's no column for which the current user has "update"
|
|
-- permissions, this functions returns an enum that only contains a
|
|
-- placeholder, so as to still allow this type to exist in the schema.
|
|
tableUpdateColumnsEnum
|
|
:: forall m n b
|
|
. (BackendSchema b, MonadSchema n m, MonadError QErr m)
|
|
=> TableInfo b
|
|
-> UpdPermInfo b
|
|
-> m (Parser 'Both n (Maybe (Column b)))
|
|
tableUpdateColumnsEnum tableInfo updatePermissions = do
|
|
tableGQLName <- getTableGQLName tableInfo
|
|
columns <- tableUpdateColumns tableInfo updatePermissions
|
|
let enumName = tableGQLName <> $$(G.litName "_update_column")
|
|
tableName = tableInfoName tableInfo
|
|
enumDesc = Just $ G.Description $ "update columns of table " <>> tableName
|
|
altDesc = Just $ G.Description $ "placeholder for update columns of table " <> tableName <<> " (current role has no relevant permissions)"
|
|
enumValues = do
|
|
column <- columns
|
|
pure (define $ pgiName column, Just $ pgiColumn column)
|
|
pure $ case nonEmpty enumValues of
|
|
Just values -> P.enum enumName enumDesc values
|
|
Nothing -> P.enum enumName altDesc $ pure (placeholder, Nothing)
|
|
where
|
|
define name = P.mkDefinition name (Just $ G.Description "column name") P.EnumValueInfo
|
|
placeholder = P.mkDefinition $$(G.litName "_PLACEHOLDER") (Just $ G.Description "placeholder (do not use)") P.EnumValueInfo
|
|
|
|
tablePermissions
|
|
:: forall m n r b. (Backend b, MonadSchema n m, MonadRole r m)
|
|
=> TableInfo b
|
|
-> m (Maybe (RolePermInfo b))
|
|
tablePermissions tableInfo = do
|
|
roleName <- askRoleName
|
|
pure $ getRolePermInfo roleName tableInfo
|
|
|
|
tableSelectPermissions
|
|
:: forall b r m n. (Backend b, MonadSchema n m, MonadRole r m)
|
|
=> TableInfo b
|
|
-> m (Maybe (SelPermInfo b))
|
|
tableSelectPermissions tableInfo = (_permSel =<<) <$> tablePermissions tableInfo
|
|
|
|
tableSelectFields
|
|
:: forall m n r b. (Backend b, MonadSchema n m, MonadTableInfo r m, MonadRole r m)
|
|
=> SourceName
|
|
-> TableInfo b
|
|
-> SelPermInfo b
|
|
-> m [FieldInfo b]
|
|
tableSelectFields sourceName tableInfo permissions = do
|
|
let tableFields = _tciFieldInfoMap . _tiCoreInfo $ tableInfo
|
|
filterM canBeSelected $ Map.elems tableFields
|
|
where
|
|
canBeSelected (FIColumn columnInfo) =
|
|
pure $ Map.member (pgiColumn columnInfo) (spiCols permissions)
|
|
canBeSelected (FIRelationship relationshipInfo) = do
|
|
tableInfo' <- askTableInfo sourceName $ riRTable relationshipInfo
|
|
isJust <$> tableSelectPermissions @b tableInfo'
|
|
canBeSelected (FIComputedField computedFieldInfo) =
|
|
case _cfiReturnType computedFieldInfo of
|
|
CFRScalar _ ->
|
|
pure $ Map.member (_cfiName computedFieldInfo) $ spiScalarComputedFields permissions
|
|
CFRSetofTable tableName -> do
|
|
tableInfo' <- askTableInfo sourceName tableName
|
|
isJust <$> tableSelectPermissions @b tableInfo'
|
|
canBeSelected (FIRemoteRelationship _) = pure True
|
|
|
|
tableColumns
|
|
:: forall b. TableInfo b -> [ColumnInfo b]
|
|
tableColumns tableInfo =
|
|
mapMaybe columnInfo . Map.elems . _tciFieldInfoMap . _tiCoreInfo $ tableInfo
|
|
where
|
|
columnInfo (FIColumn ci) = Just ci
|
|
columnInfo _ = Nothing
|
|
|
|
tableSelectColumns
|
|
:: forall m n r b. (Backend b, MonadSchema n m, MonadTableInfo r m, MonadRole r m)
|
|
=> SourceName
|
|
-> TableInfo b
|
|
-> SelPermInfo b
|
|
-> m [ColumnInfo b]
|
|
tableSelectColumns sourceName tableInfo permissions =
|
|
mapMaybe columnInfo <$> tableSelectFields sourceName tableInfo permissions
|
|
where
|
|
columnInfo (FIColumn ci) = Just ci
|
|
columnInfo _ = Nothing
|
|
|
|
tableUpdateColumns
|
|
:: forall m n b. (Backend b, MonadSchema n m)
|
|
=> TableInfo b
|
|
-> UpdPermInfo b
|
|
-> m [ColumnInfo b]
|
|
tableUpdateColumns tableInfo permissions = do
|
|
let tableFields = _tciFieldInfoMap . _tiCoreInfo $ tableInfo
|
|
pure $ mapMaybe isUpdatable $ Map.elems tableFields
|
|
where
|
|
isUpdatable (FIColumn columnInfo) =
|
|
if Set.member (pgiColumn columnInfo) (upiCols permissions)
|
|
&& not (Map.member (pgiColumn columnInfo) (upiSet permissions))
|
|
then Just columnInfo
|
|
else Nothing
|
|
isUpdatable _ = Nothing
|