2021-11-10 03:37:42 +03:00
|
|
|
-- | This module provides building blocks for the GraphQL Schema that the
|
|
|
|
-- GraphQL Engine presents.
|
|
|
|
--
|
|
|
|
-- The functions defined here are used to serve as default implementations for
|
|
|
|
-- their namesakes in the 'BackendSchema' type class.
|
|
|
|
--
|
|
|
|
-- When, for some backend, you want to implement a new feature that manifests
|
|
|
|
-- itself visibly in the schema (e.g., if you're developing support for update
|
|
|
|
-- mutations), this module is likely where your efforts should start.
|
|
|
|
--
|
|
|
|
-- Using these functions help us present a consistent GraphQL schema across
|
|
|
|
-- different backends.
|
|
|
|
--
|
|
|
|
-- There is a bit of tension however, as sometimes we intentionally do want the
|
|
|
|
-- GraphQL Schema relating to some backend to be different in some way.
|
|
|
|
--
|
|
|
|
-- It could be that a backend only has limited support for some common feature,
|
|
|
|
-- or, more interestingly, that some backend just does things differently (c.f.
|
|
|
|
-- MSSQL's @MERGE@ statement with PostgreSQL's @INSERT .. ON CONFLICT@, which
|
|
|
|
-- are similar enough that we want to use the same overall upsert schema but
|
|
|
|
-- different enough that we want to use different field names)
|
|
|
|
--
|
|
|
|
-- When you want to implement new schema for a backend, there is overall three
|
|
|
|
-- different ways do deal with this tension:
|
|
|
|
--
|
|
|
|
-- 1. You can duplicate existing code and implement the new behavior in the
|
|
|
|
-- duplicate.
|
|
|
|
-- 2. You can infuse the new behavior into existing code and switch dynamically
|
|
|
|
-- at runtime (or via type class instance dispatch, which is the same
|
|
|
|
-- for our purposes)
|
|
|
|
-- 3. You can refactor the existing building blocks and compose them differently
|
|
|
|
-- at use sites to get the desired behavior nuances.
|
|
|
|
--
|
|
|
|
-- Of these three, steps 1. and 2. are by far the easiest to execute, while 3.
|
|
|
|
-- requires some critical thought. However, both 1. and 2. produce legacy code
|
|
|
|
-- that is difficult to maintain and understand.
|
|
|
|
--
|
|
|
|
-- As a guideline, if you find yourself wanting add new behavior to some of
|
|
|
|
-- these functions it's very likely that you should consider refactoring them
|
|
|
|
-- instead, thus shifting the responsibility deciding on the correct behavior to
|
|
|
|
-- use sites.
|
|
|
|
--
|
|
|
|
-- It an ongoing effort to adapt and refactor these building blocks such that
|
|
|
|
-- they have the sizes and shapes that result in the most elegant uses of them
|
|
|
|
-- that we can manage.
|
2021-11-04 19:08:33 +03:00
|
|
|
module Hasura.GraphQL.Schema.Build
|
2022-05-25 13:24:41 +03:00
|
|
|
( buildFunctionMutationFieldsPG,
|
|
|
|
buildFunctionQueryFieldsPG,
|
2021-11-04 19:08:33 +03:00
|
|
|
buildTableDeleteMutationFields,
|
|
|
|
buildTableInsertMutationFields,
|
2022-06-07 08:32:08 +03:00
|
|
|
buildTableQueryAndSubscriptionFields,
|
2022-04-22 22:53:12 +03:00
|
|
|
buildTableStreamingSubscriptionFields,
|
2021-11-04 19:08:33 +03:00
|
|
|
buildTableUpdateMutationFields,
|
|
|
|
)
|
|
|
|
where
|
2021-02-09 15:47:21 +03:00
|
|
|
|
2022-05-26 14:54:30 +03:00
|
|
|
import Data.Has (getter)
|
|
|
|
import Data.Text.Casing qualified as C
|
2021-09-24 01:56:37 +03:00
|
|
|
import Data.Text.Extended
|
|
|
|
import Hasura.GraphQL.Parser hiding (EnumValueInfo, field)
|
|
|
|
import Hasura.GraphQL.Schema.Backend (MonadBuildSchema)
|
|
|
|
import Hasura.GraphQL.Schema.Common
|
|
|
|
import Hasura.GraphQL.Schema.Mutation
|
|
|
|
import Hasura.GraphQL.Schema.Select
|
2022-04-22 22:53:12 +03:00
|
|
|
import Hasura.GraphQL.Schema.SubscriptionStream (selectStreamTable)
|
2022-06-07 08:32:08 +03:00
|
|
|
import Hasura.GraphQL.Schema.Table (tableSelectPermissions)
|
2021-11-26 16:47:12 +03:00
|
|
|
import Hasura.GraphQL.Schema.Update (updateTable, updateTableByPk)
|
2021-09-24 01:56:37 +03:00
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.IR
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Backend
|
|
|
|
import Hasura.RQL.Types.Common
|
|
|
|
import Hasura.RQL.Types.Function
|
2022-06-07 08:32:08 +03:00
|
|
|
import Hasura.RQL.Types.Permission
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.SchemaCache
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
import Hasura.RQL.Types.Source
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.SourceCustomization
|
|
|
|
import Hasura.RQL.Types.Table
|
2022-05-25 13:24:41 +03:00
|
|
|
import Hasura.SQL.Backend
|
2022-06-07 08:32:08 +03:00
|
|
|
import Hasura.Server.Types (StreamingSubscriptionsCtx (..))
|
2021-09-24 01:56:37 +03:00
|
|
|
import Language.GraphQL.Draft.Syntax qualified as G
|
2021-02-09 15:47:21 +03:00
|
|
|
|
2022-05-26 14:54:30 +03:00
|
|
|
-- | Builds field name with proper case. Please note that this is a pure
|
|
|
|
-- function as all the validation has already been done while preparing
|
|
|
|
-- @GQLNameIdentifier@.
|
|
|
|
setFieldNameCase ::
|
|
|
|
NamingCase ->
|
|
|
|
TableInfo b ->
|
|
|
|
CustomRootField ->
|
|
|
|
(C.GQLNameIdentifier -> C.GQLNameIdentifier) ->
|
|
|
|
C.GQLNameIdentifier ->
|
|
|
|
G.Name
|
|
|
|
setFieldNameCase tCase tInfo crf getFieldName tableName =
|
|
|
|
(applyFieldNameCaseIdentifier tCase fieldIdentifier)
|
|
|
|
where
|
|
|
|
tccName = fmap (`C.Identifier` []) . _tcCustomName . _tciCustomConfig . _tiCoreInfo $ tInfo
|
|
|
|
crfName = fmap (`C.Identifier` []) (_crfName crf)
|
|
|
|
fieldIdentifier = fromMaybe (getFieldName (fromMaybe tableName tccName)) crfName
|
|
|
|
|
2022-06-07 08:32:08 +03:00
|
|
|
-- | buildTableQueryAndSubscriptionFields builds the field parsers of a table.
|
|
|
|
-- It returns a tuple with array of field parsers that correspond to the field
|
|
|
|
-- parsers of the query root and the field parsers of the subscription root
|
|
|
|
buildTableQueryAndSubscriptionFields ::
|
2021-09-24 01:56:37 +03:00
|
|
|
forall b r m n.
|
|
|
|
MonadBuildSchema b r m n =>
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
SourceInfo b ->
|
2021-09-24 01:56:37 +03:00
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
2022-06-07 08:32:08 +03:00
|
|
|
StreamingSubscriptionsCtx ->
|
2022-05-26 14:54:30 +03:00
|
|
|
C.GQLNameIdentifier ->
|
2022-06-07 08:32:08 +03:00
|
|
|
m
|
|
|
|
( [FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))],
|
|
|
|
[FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
|
|
|
)
|
|
|
|
buildTableQueryAndSubscriptionFields sourceInfo tableName tableInfo streamSubCtx gqlName = do
|
2022-05-26 14:54:30 +03:00
|
|
|
tCase <- asks getter
|
2022-02-28 10:49:13 +03:00
|
|
|
-- select table
|
2022-05-26 14:54:30 +03:00
|
|
|
selectName <- mkRootFieldName $ setFieldNameCase tCase tableInfo _tcrfSelect mkSelectField gqlName
|
2022-02-28 10:49:13 +03:00
|
|
|
-- select table by pk
|
2022-05-26 14:54:30 +03:00
|
|
|
selectPKName <- mkRootFieldName $ setFieldNameCase tCase tableInfo _tcrfSelectByPk mkSelectByPkField gqlName
|
2022-02-28 10:49:13 +03:00
|
|
|
-- select table aggregate
|
2022-05-26 14:54:30 +03:00
|
|
|
selectAggName <- mkRootFieldName $ setFieldNameCase tCase tableInfo _tcrfSelectAggregate mkSelectAggregateField gqlName
|
2022-06-07 08:32:08 +03:00
|
|
|
|
|
|
|
selectPermission <- tableSelectPermissions tableInfo
|
|
|
|
|
|
|
|
selectTableParser <- optionalFieldParser QDBMultipleRows $ selectTable sourceInfo tableInfo selectName selectDesc
|
|
|
|
selectTableByPkParser <- optionalFieldParser QDBSingleRow $ selectTableByPk sourceInfo tableInfo selectPKName selectPKDesc
|
|
|
|
selectTableAggregateParser <- optionalFieldParser QDBAggregation $ selectTableAggregate sourceInfo tableInfo selectAggName selectAggDesc
|
|
|
|
|
|
|
|
case selectPermission of
|
|
|
|
-- No select permission found for the current role, so
|
|
|
|
-- no root fields will be accessible to the role
|
|
|
|
Nothing -> pure (mempty, mempty)
|
|
|
|
-- Filter the root fields which have been enabled
|
|
|
|
Just SelPermInfo {..} -> do
|
|
|
|
selectStreamParser <-
|
|
|
|
if (isRootFieldAllowed SRFTSelectStream spiAllowedSubscriptionRootFields && streamSubCtx == StreamingSubscriptionsEnabled)
|
|
|
|
then buildTableStreamingSubscriptionFields sourceInfo tableName tableInfo gqlName
|
|
|
|
else pure mempty
|
|
|
|
|
|
|
|
let (querySelectTableParser, subscriptionSelectTableParser) =
|
|
|
|
getQueryAndSubscriptionRootFields
|
|
|
|
selectTableParser
|
|
|
|
(isRootFieldAllowed QRFTSelect spiAllowedQueryRootFields)
|
|
|
|
(isRootFieldAllowed SRFTSelect spiAllowedSubscriptionRootFields)
|
|
|
|
|
|
|
|
(querySelectTableByPkParser, subscriptionSelectTableByPkParser) =
|
|
|
|
getQueryAndSubscriptionRootFields
|
|
|
|
selectTableByPkParser
|
|
|
|
(isRootFieldAllowed QRFTSelectByPk spiAllowedQueryRootFields)
|
|
|
|
(isRootFieldAllowed SRFTSelectByPk spiAllowedSubscriptionRootFields)
|
|
|
|
|
|
|
|
(querySelectTableAggParser, subscriptionSelectTableAggParser) =
|
|
|
|
getQueryAndSubscriptionRootFields
|
|
|
|
selectTableAggregateParser
|
|
|
|
(isRootFieldAllowed QRFTSelectAggregate spiAllowedQueryRootFields)
|
|
|
|
(isRootFieldAllowed SRFTSelectAggregate spiAllowedSubscriptionRootFields)
|
|
|
|
|
|
|
|
queryRootFields = catMaybes [querySelectTableParser, querySelectTableByPkParser, querySelectTableAggParser]
|
|
|
|
subscriptionRootFields =
|
|
|
|
selectStreamParser
|
|
|
|
<> catMaybes [subscriptionSelectTableParser, subscriptionSelectTableByPkParser, subscriptionSelectTableAggParser]
|
|
|
|
|
|
|
|
pure (queryRootFields, subscriptionRootFields)
|
2022-02-28 10:49:13 +03:00
|
|
|
where
|
|
|
|
selectDesc = buildFieldDescription defaultSelectDesc $ _crfComment _tcrfSelect
|
|
|
|
selectPKDesc = buildFieldDescription defaultSelectPKDesc $ _crfComment _tcrfSelectByPk
|
|
|
|
selectAggDesc = buildFieldDescription defaultSelectAggDesc $ _crfComment _tcrfSelectAggregate
|
|
|
|
defaultSelectDesc = "fetch data from the table: " <>> tableName
|
|
|
|
defaultSelectPKDesc = "fetch data from the table: " <> tableName <<> " using primary key columns"
|
|
|
|
defaultSelectAggDesc = "fetch aggregated fields from the table: " <>> tableName
|
|
|
|
TableCustomRootFields {..} = _tcCustomRootFields . _tciCustomConfig $ _tiCoreInfo tableInfo
|
2021-02-09 15:47:21 +03:00
|
|
|
|
2022-06-07 08:32:08 +03:00
|
|
|
-- This function checks if a root field is allowed to be exposed
|
|
|
|
-- in the query root and a subscription root and when it is allowed,
|
|
|
|
-- the parser will be returned.
|
|
|
|
getQueryAndSubscriptionRootFields parser allowedInQuery allowedInSubscription =
|
|
|
|
case (allowedInQuery, allowedInSubscription) of
|
|
|
|
(True, True) -> (parser, parser)
|
|
|
|
(True, False) -> (parser, Nothing)
|
|
|
|
(False, True) -> (Nothing, parser)
|
|
|
|
(False, False) -> (Nothing, Nothing)
|
|
|
|
|
2022-04-22 22:53:12 +03:00
|
|
|
buildTableStreamingSubscriptionFields ::
|
|
|
|
forall b r m n.
|
|
|
|
MonadBuildSchema b r m n =>
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
SourceInfo b ->
|
2022-04-22 22:53:12 +03:00
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
2022-05-26 14:54:30 +03:00
|
|
|
C.GQLNameIdentifier ->
|
2022-04-22 22:53:12 +03:00
|
|
|
m [FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2022-06-07 08:32:08 +03:00
|
|
|
buildTableStreamingSubscriptionFields sourceInfo tableName tableInfo tableIdentifier = do
|
2022-05-26 14:54:30 +03:00
|
|
|
tCase <- asks getter
|
2022-04-22 22:53:12 +03:00
|
|
|
let customRootFields = _tcCustomRootFields $ _tciCustomConfig $ _tiCoreInfo tableInfo
|
|
|
|
selectDesc = Just $ G.Description $ "fetch data from the table in a streaming manner : " <>> tableName
|
2022-06-07 08:32:08 +03:00
|
|
|
selectStreamName <-
|
|
|
|
mkRootFieldName $ setFieldNameCase tCase tableInfo (_tcrfSelect customRootFields) mkSelectStreamField tableIdentifier
|
2022-04-22 22:53:12 +03:00
|
|
|
catMaybes
|
|
|
|
<$> sequenceA
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
[ optionalFieldParser QDBStreamMultipleRows $ selectStreamTable sourceInfo tableInfo selectStreamName selectDesc
|
2022-04-22 22:53:12 +03:00
|
|
|
]
|
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
buildTableInsertMutationFields ::
|
|
|
|
forall b r m n.
|
|
|
|
MonadBuildSchema b r m n =>
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
(SourceInfo b -> TableInfo b -> m (InputFieldsParser n (BackendInsert b (UnpreparedValue b)))) ->
|
Role-invariant schema constructors
We build the GraphQL schema by combining building blocks such as `tableSelectionSet` and `columnParser`. These building blocks individually build `{InputFields,Field,}Parser` objects. Those object specify the valid GraphQL schema.
Since the GraphQL schema is role-dependent, at some point we need to know what fragment of the GraphQL schema a specific role is allowed to access, and this is stored in `{Sel,Upd,Ins,Del}PermInfo` objects.
We have passed around these permission objects as function arguments to the schema building blocks since we first started dealing with permissions during the PDV refactor - see hasura/graphql-engine@5168b99e463199b1934d8645bd6cd37eddb64ae1 in hasura/graphql-engine#4111. This means that, for instance, `tableSelectionSet` has as its type:
```haskell
tableSelectionSet ::
forall b r m n.
MonadBuildSchema b r m n =>
SourceName ->
TableInfo b ->
SelPermInfo b ->
m (Parser 'Output n (AnnotatedFields b))
```
There are three reasons to change this.
1. We often pass a `Maybe (xPermInfo b)` instead of a proper `xPermInfo b`, and it's not clear what the intended semantics of this is. Some potential improvements on the data types involved are discussed in issue hasura/graphql-engine-mono#3125.
2. In most cases we also already pass a `TableInfo b`, and together with the `MonadRole` that is usually also in scope, this means that we could look up the required permissions regardless: so passing the permissions explicitly undermines the "single source of truth" principle. Breaking this principle also makes the code more difficult to read.
3. We are working towards role-based parsers (see hasura/graphql-engine-mono#2711), where the `{InputFields,Field,}Parser` objects are constructed in a role-invariant way, so that we have a single object that can be used for all roles. In particular, this means that the schema building blocks _need_ to be constructed in a role-invariant way. While this PR doesn't accomplish that, it does reduce the amount of role-specific arguments being passed, thus fixing hasura/graphql-engine-mono#3068.
Concretely, this PR simply drops the `xPermInfo b` argument from almost all schema building blocks. Instead these objects are looked up from the `TableInfo b` as-needed. The resulting code is considerably simpler and shorter.
One way to interpret this change is as follows. Before this PR, we figured out permissions at the top-level in `Hasura.GraphQL.Schema`, passing down the obtained `xPermInfo` objects as required. After this PR, we have a bottom-up approach where the schema building blocks themselves decide whether they want to be included for a particular role.
So this moves some permission logic out of `Hasura.GraphQL.Schema`, which is very complex.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3608
GitOrigin-RevId: 51a744f34ec7d57bc8077667ae7f9cb9c4f6c962
2022-02-17 11:16:20 +03:00
|
|
|
Scenario ->
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
SourceInfo b ->
|
2021-09-24 01:56:37 +03:00
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
2022-05-26 14:54:30 +03:00
|
|
|
C.GQLNameIdentifier ->
|
2022-04-01 09:43:05 +03:00
|
|
|
m [FieldParser n (AnnotatedInsert b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
buildTableInsertMutationFields backendInsertAction scenario sourceInfo tableName tableInfo gqlName = do
|
2022-05-26 14:54:30 +03:00
|
|
|
tCase <- asks getter
|
|
|
|
-- insert in table
|
|
|
|
insertName <- mkRootFieldName $ setFieldNameCase tCase tableInfo _tcrfInsert mkInsertField gqlName
|
|
|
|
-- insert one in table
|
|
|
|
insertOneName <- mkRootFieldName $ setFieldNameCase tCase tableInfo _tcrfInsertOne mkInsertOneField gqlName
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
insert <- insertIntoTable backendInsertAction scenario sourceInfo tableInfo insertName insertDesc
|
Role-invariant schema constructors
We build the GraphQL schema by combining building blocks such as `tableSelectionSet` and `columnParser`. These building blocks individually build `{InputFields,Field,}Parser` objects. Those object specify the valid GraphQL schema.
Since the GraphQL schema is role-dependent, at some point we need to know what fragment of the GraphQL schema a specific role is allowed to access, and this is stored in `{Sel,Upd,Ins,Del}PermInfo` objects.
We have passed around these permission objects as function arguments to the schema building blocks since we first started dealing with permissions during the PDV refactor - see hasura/graphql-engine@5168b99e463199b1934d8645bd6cd37eddb64ae1 in hasura/graphql-engine#4111. This means that, for instance, `tableSelectionSet` has as its type:
```haskell
tableSelectionSet ::
forall b r m n.
MonadBuildSchema b r m n =>
SourceName ->
TableInfo b ->
SelPermInfo b ->
m (Parser 'Output n (AnnotatedFields b))
```
There are three reasons to change this.
1. We often pass a `Maybe (xPermInfo b)` instead of a proper `xPermInfo b`, and it's not clear what the intended semantics of this is. Some potential improvements on the data types involved are discussed in issue hasura/graphql-engine-mono#3125.
2. In most cases we also already pass a `TableInfo b`, and together with the `MonadRole` that is usually also in scope, this means that we could look up the required permissions regardless: so passing the permissions explicitly undermines the "single source of truth" principle. Breaking this principle also makes the code more difficult to read.
3. We are working towards role-based parsers (see hasura/graphql-engine-mono#2711), where the `{InputFields,Field,}Parser` objects are constructed in a role-invariant way, so that we have a single object that can be used for all roles. In particular, this means that the schema building blocks _need_ to be constructed in a role-invariant way. While this PR doesn't accomplish that, it does reduce the amount of role-specific arguments being passed, thus fixing hasura/graphql-engine-mono#3068.
Concretely, this PR simply drops the `xPermInfo b` argument from almost all schema building blocks. Instead these objects are looked up from the `TableInfo b` as-needed. The resulting code is considerably simpler and shorter.
One way to interpret this change is as follows. Before this PR, we figured out permissions at the top-level in `Hasura.GraphQL.Schema`, passing down the obtained `xPermInfo` objects as required. After this PR, we have a bottom-up approach where the schema building blocks themselves decide whether they want to be included for a particular role.
So this moves some permission logic out of `Hasura.GraphQL.Schema`, which is very complex.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3608
GitOrigin-RevId: 51a744f34ec7d57bc8077667ae7f9cb9c4f6c962
2022-02-17 11:16:20 +03:00
|
|
|
-- Select permissions are required for insertOne: the selection set is the
|
|
|
|
-- same as a select on that table, and it therefore can't be populated if the
|
|
|
|
-- user doesn't have select permissions.
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
insertOne <- insertOneIntoTable backendInsertAction scenario sourceInfo tableInfo insertOneName insertOneDesc
|
Role-invariant schema constructors
We build the GraphQL schema by combining building blocks such as `tableSelectionSet` and `columnParser`. These building blocks individually build `{InputFields,Field,}Parser` objects. Those object specify the valid GraphQL schema.
Since the GraphQL schema is role-dependent, at some point we need to know what fragment of the GraphQL schema a specific role is allowed to access, and this is stored in `{Sel,Upd,Ins,Del}PermInfo` objects.
We have passed around these permission objects as function arguments to the schema building blocks since we first started dealing with permissions during the PDV refactor - see hasura/graphql-engine@5168b99e463199b1934d8645bd6cd37eddb64ae1 in hasura/graphql-engine#4111. This means that, for instance, `tableSelectionSet` has as its type:
```haskell
tableSelectionSet ::
forall b r m n.
MonadBuildSchema b r m n =>
SourceName ->
TableInfo b ->
SelPermInfo b ->
m (Parser 'Output n (AnnotatedFields b))
```
There are three reasons to change this.
1. We often pass a `Maybe (xPermInfo b)` instead of a proper `xPermInfo b`, and it's not clear what the intended semantics of this is. Some potential improvements on the data types involved are discussed in issue hasura/graphql-engine-mono#3125.
2. In most cases we also already pass a `TableInfo b`, and together with the `MonadRole` that is usually also in scope, this means that we could look up the required permissions regardless: so passing the permissions explicitly undermines the "single source of truth" principle. Breaking this principle also makes the code more difficult to read.
3. We are working towards role-based parsers (see hasura/graphql-engine-mono#2711), where the `{InputFields,Field,}Parser` objects are constructed in a role-invariant way, so that we have a single object that can be used for all roles. In particular, this means that the schema building blocks _need_ to be constructed in a role-invariant way. While this PR doesn't accomplish that, it does reduce the amount of role-specific arguments being passed, thus fixing hasura/graphql-engine-mono#3068.
Concretely, this PR simply drops the `xPermInfo b` argument from almost all schema building blocks. Instead these objects are looked up from the `TableInfo b` as-needed. The resulting code is considerably simpler and shorter.
One way to interpret this change is as follows. Before this PR, we figured out permissions at the top-level in `Hasura.GraphQL.Schema`, passing down the obtained `xPermInfo` objects as required. After this PR, we have a bottom-up approach where the schema building blocks themselves decide whether they want to be included for a particular role.
So this moves some permission logic out of `Hasura.GraphQL.Schema`, which is very complex.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3608
GitOrigin-RevId: 51a744f34ec7d57bc8077667ae7f9cb9c4f6c962
2022-02-17 11:16:20 +03:00
|
|
|
pure $ catMaybes [insert, insertOne]
|
2022-02-28 10:49:13 +03:00
|
|
|
where
|
|
|
|
insertDesc = buildFieldDescription defaultInsertDesc $ _crfComment _tcrfInsert
|
|
|
|
insertOneDesc = buildFieldDescription defaultInsertOneDesc $ _crfComment _tcrfInsertOne
|
|
|
|
defaultInsertDesc = "insert data into the table: " <>> tableName
|
|
|
|
defaultInsertOneDesc = "insert a single row into the table: " <>> tableName
|
|
|
|
TableCustomRootFields {..} = _tcCustomRootFields . _tciCustomConfig $ _tiCoreInfo tableInfo
|
2021-02-09 15:47:21 +03:00
|
|
|
|
2021-11-10 03:37:42 +03:00
|
|
|
-- | This function is the basic building block for update mutations. It
|
|
|
|
-- implements the mutation schema in the general shape described in
|
|
|
|
-- @https://hasura.io/docs/latest/graphql/core/databases/postgres/mutations/update.html@.
|
|
|
|
--
|
|
|
|
-- Something that varies between backends is the @update operators@ that they
|
|
|
|
-- support (i.e. the schema fields @_set@, @_inc@, etc., see
|
|
|
|
-- <src/Hasura.Backends.Postgres.Instances.Schema.html#updateOperators Hasura.Backends.Postgres.Instances.Schema.updateOperators> for an example
|
|
|
|
-- implementation). Therefore, this function is parameterised over a monadic
|
|
|
|
-- action that produces the operators that the backend supports in the context
|
|
|
|
-- of some table and associated update permissions.
|
|
|
|
--
|
|
|
|
-- Apart from this detail, the rest of the arguments are the same as those
|
|
|
|
-- of @BackendSchema.@'Hasura.GraphQL.Schema.Backend.buildTableUpdateMutationFields'.
|
|
|
|
--
|
|
|
|
-- The suggested way to use this is like:
|
|
|
|
--
|
|
|
|
-- > instance BackendSchema MyBackend where
|
|
|
|
-- > ...
|
|
|
|
-- > buildTableUpdateMutationFields = GSB.buildTableUpdateMutationFields myBackendUpdateOperators
|
|
|
|
-- > ...
|
2021-09-24 01:56:37 +03:00
|
|
|
buildTableUpdateMutationFields ::
|
|
|
|
forall b r m n.
|
|
|
|
MonadBuildSchema b r m n =>
|
2021-11-25 00:39:42 +03:00
|
|
|
-- | an action that builds @BackendUpdate@ with the
|
|
|
|
-- backend-specific data needed to perform an update mutation
|
2021-11-08 21:11:44 +03:00
|
|
|
( TableInfo b ->
|
|
|
|
m
|
2021-11-18 21:02:58 +03:00
|
|
|
(InputFieldsParser n (BackendUpdate b (UnpreparedValue b)))
|
2021-11-08 21:11:44 +03:00
|
|
|
) ->
|
2022-05-31 17:41:09 +03:00
|
|
|
Scenario ->
|
2021-11-10 03:37:42 +03:00
|
|
|
-- | The source that the table lives in
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
SourceInfo b ->
|
2021-11-10 03:37:42 +03:00
|
|
|
-- | The name of the table being acted on
|
2021-09-24 01:56:37 +03:00
|
|
|
TableName b ->
|
2021-11-10 03:37:42 +03:00
|
|
|
-- | table info
|
2021-09-24 01:56:37 +03:00
|
|
|
TableInfo b ->
|
2021-11-10 03:37:42 +03:00
|
|
|
-- | field display name
|
2022-05-26 14:54:30 +03:00
|
|
|
C.GQLNameIdentifier ->
|
2021-12-07 16:12:02 +03:00
|
|
|
m [FieldParser n (AnnotatedUpdateG b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2022-05-31 17:41:09 +03:00
|
|
|
buildTableUpdateMutationFields mkBackendUpdate scenario sourceInfo tableName tableInfo gqlName = do
|
2022-05-26 14:54:30 +03:00
|
|
|
tCase <- asks getter
|
Role-invariant schema constructors
We build the GraphQL schema by combining building blocks such as `tableSelectionSet` and `columnParser`. These building blocks individually build `{InputFields,Field,}Parser` objects. Those object specify the valid GraphQL schema.
Since the GraphQL schema is role-dependent, at some point we need to know what fragment of the GraphQL schema a specific role is allowed to access, and this is stored in `{Sel,Upd,Ins,Del}PermInfo` objects.
We have passed around these permission objects as function arguments to the schema building blocks since we first started dealing with permissions during the PDV refactor - see hasura/graphql-engine@5168b99e463199b1934d8645bd6cd37eddb64ae1 in hasura/graphql-engine#4111. This means that, for instance, `tableSelectionSet` has as its type:
```haskell
tableSelectionSet ::
forall b r m n.
MonadBuildSchema b r m n =>
SourceName ->
TableInfo b ->
SelPermInfo b ->
m (Parser 'Output n (AnnotatedFields b))
```
There are three reasons to change this.
1. We often pass a `Maybe (xPermInfo b)` instead of a proper `xPermInfo b`, and it's not clear what the intended semantics of this is. Some potential improvements on the data types involved are discussed in issue hasura/graphql-engine-mono#3125.
2. In most cases we also already pass a `TableInfo b`, and together with the `MonadRole` that is usually also in scope, this means that we could look up the required permissions regardless: so passing the permissions explicitly undermines the "single source of truth" principle. Breaking this principle also makes the code more difficult to read.
3. We are working towards role-based parsers (see hasura/graphql-engine-mono#2711), where the `{InputFields,Field,}Parser` objects are constructed in a role-invariant way, so that we have a single object that can be used for all roles. In particular, this means that the schema building blocks _need_ to be constructed in a role-invariant way. While this PR doesn't accomplish that, it does reduce the amount of role-specific arguments being passed, thus fixing hasura/graphql-engine-mono#3068.
Concretely, this PR simply drops the `xPermInfo b` argument from almost all schema building blocks. Instead these objects are looked up from the `TableInfo b` as-needed. The resulting code is considerably simpler and shorter.
One way to interpret this change is as follows. Before this PR, we figured out permissions at the top-level in `Hasura.GraphQL.Schema`, passing down the obtained `xPermInfo` objects as required. After this PR, we have a bottom-up approach where the schema building blocks themselves decide whether they want to be included for a particular role.
So this moves some permission logic out of `Hasura.GraphQL.Schema`, which is very complex.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3608
GitOrigin-RevId: 51a744f34ec7d57bc8077667ae7f9cb9c4f6c962
2022-02-17 11:16:20 +03:00
|
|
|
backendUpdate <- mkBackendUpdate tableInfo
|
2022-02-28 10:49:13 +03:00
|
|
|
-- update table
|
2022-05-26 14:54:30 +03:00
|
|
|
updateName <- mkRootFieldName $ setFieldNameCase tCase tableInfo _tcrfUpdate mkUpdateField gqlName
|
2022-02-28 10:49:13 +03:00
|
|
|
-- update table by pk
|
2022-05-26 14:54:30 +03:00
|
|
|
updatePKName <- mkRootFieldName $ setFieldNameCase tCase tableInfo _tcrfUpdateByPk mkUpdateByPkField gqlName
|
2022-05-31 17:41:09 +03:00
|
|
|
update <- updateTable backendUpdate scenario sourceInfo tableInfo updateName updateDesc
|
2021-11-08 21:11:44 +03:00
|
|
|
-- Primary keys can only be tested in the `where` clause if a primary key
|
|
|
|
-- exists on the table and if the user has select permissions on all columns
|
|
|
|
-- that make up the key.
|
2022-05-31 17:41:09 +03:00
|
|
|
updateByPk <- updateTableByPk backendUpdate scenario sourceInfo tableInfo updatePKName updatePKDesc
|
Role-invariant schema constructors
We build the GraphQL schema by combining building blocks such as `tableSelectionSet` and `columnParser`. These building blocks individually build `{InputFields,Field,}Parser` objects. Those object specify the valid GraphQL schema.
Since the GraphQL schema is role-dependent, at some point we need to know what fragment of the GraphQL schema a specific role is allowed to access, and this is stored in `{Sel,Upd,Ins,Del}PermInfo` objects.
We have passed around these permission objects as function arguments to the schema building blocks since we first started dealing with permissions during the PDV refactor - see hasura/graphql-engine@5168b99e463199b1934d8645bd6cd37eddb64ae1 in hasura/graphql-engine#4111. This means that, for instance, `tableSelectionSet` has as its type:
```haskell
tableSelectionSet ::
forall b r m n.
MonadBuildSchema b r m n =>
SourceName ->
TableInfo b ->
SelPermInfo b ->
m (Parser 'Output n (AnnotatedFields b))
```
There are three reasons to change this.
1. We often pass a `Maybe (xPermInfo b)` instead of a proper `xPermInfo b`, and it's not clear what the intended semantics of this is. Some potential improvements on the data types involved are discussed in issue hasura/graphql-engine-mono#3125.
2. In most cases we also already pass a `TableInfo b`, and together with the `MonadRole` that is usually also in scope, this means that we could look up the required permissions regardless: so passing the permissions explicitly undermines the "single source of truth" principle. Breaking this principle also makes the code more difficult to read.
3. We are working towards role-based parsers (see hasura/graphql-engine-mono#2711), where the `{InputFields,Field,}Parser` objects are constructed in a role-invariant way, so that we have a single object that can be used for all roles. In particular, this means that the schema building blocks _need_ to be constructed in a role-invariant way. While this PR doesn't accomplish that, it does reduce the amount of role-specific arguments being passed, thus fixing hasura/graphql-engine-mono#3068.
Concretely, this PR simply drops the `xPermInfo b` argument from almost all schema building blocks. Instead these objects are looked up from the `TableInfo b` as-needed. The resulting code is considerably simpler and shorter.
One way to interpret this change is as follows. Before this PR, we figured out permissions at the top-level in `Hasura.GraphQL.Schema`, passing down the obtained `xPermInfo` objects as required. After this PR, we have a bottom-up approach where the schema building blocks themselves decide whether they want to be included for a particular role.
So this moves some permission logic out of `Hasura.GraphQL.Schema`, which is very complex.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3608
GitOrigin-RevId: 51a744f34ec7d57bc8077667ae7f9cb9c4f6c962
2022-02-17 11:16:20 +03:00
|
|
|
pure $ catMaybes [update, updateByPk]
|
2022-02-28 10:49:13 +03:00
|
|
|
where
|
|
|
|
updateDesc = buildFieldDescription defaultUpdateDesc $ _crfComment _tcrfUpdate
|
|
|
|
updatePKDesc = buildFieldDescription defaultUpdatePKDesc $ _crfComment _tcrfUpdateByPk
|
|
|
|
defaultUpdateDesc = "update data of the table: " <>> tableName
|
|
|
|
defaultUpdatePKDesc = "update single row of the table: " <>> tableName
|
|
|
|
TableCustomRootFields {..} = _tcCustomRootFields . _tciCustomConfig $ _tiCoreInfo tableInfo
|
2021-02-09 15:47:21 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
buildTableDeleteMutationFields ::
|
|
|
|
forall b r m n.
|
|
|
|
MonadBuildSchema b r m n =>
|
2022-05-31 17:41:09 +03:00
|
|
|
Scenario ->
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
SourceInfo b ->
|
2021-09-24 01:56:37 +03:00
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
2022-05-26 14:54:30 +03:00
|
|
|
C.GQLNameIdentifier ->
|
2021-12-07 16:12:02 +03:00
|
|
|
m [FieldParser n (AnnDelG b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2022-05-31 17:41:09 +03:00
|
|
|
buildTableDeleteMutationFields scenario sourceInfo tableName tableInfo gqlName = do
|
2022-05-26 14:54:30 +03:00
|
|
|
tCase <- asks getter
|
2022-02-28 10:49:13 +03:00
|
|
|
-- delete from table
|
2022-05-26 14:54:30 +03:00
|
|
|
deleteName <- mkRootFieldName $ setFieldNameCase tCase tableInfo _tcrfDelete mkDeleteField gqlName
|
2022-02-28 10:49:13 +03:00
|
|
|
-- delete from table by pk
|
2022-05-26 14:54:30 +03:00
|
|
|
deletePKName <- mkRootFieldName $ setFieldNameCase tCase tableInfo _tcrfDeleteByPk mkDeleteByPkField gqlName
|
2022-05-31 17:41:09 +03:00
|
|
|
delete <- deleteFromTable scenario sourceInfo tableInfo deleteName deleteDesc
|
2021-02-09 15:47:21 +03:00
|
|
|
-- Primary keys can only be tested in the `where` clause if the user has
|
|
|
|
-- select permissions for them, which at the very least requires select
|
|
|
|
-- permissions.
|
2022-05-31 17:41:09 +03:00
|
|
|
deleteByPk <- deleteFromTableByPk scenario sourceInfo tableInfo deletePKName deletePKDesc
|
Role-invariant schema constructors
We build the GraphQL schema by combining building blocks such as `tableSelectionSet` and `columnParser`. These building blocks individually build `{InputFields,Field,}Parser` objects. Those object specify the valid GraphQL schema.
Since the GraphQL schema is role-dependent, at some point we need to know what fragment of the GraphQL schema a specific role is allowed to access, and this is stored in `{Sel,Upd,Ins,Del}PermInfo` objects.
We have passed around these permission objects as function arguments to the schema building blocks since we first started dealing with permissions during the PDV refactor - see hasura/graphql-engine@5168b99e463199b1934d8645bd6cd37eddb64ae1 in hasura/graphql-engine#4111. This means that, for instance, `tableSelectionSet` has as its type:
```haskell
tableSelectionSet ::
forall b r m n.
MonadBuildSchema b r m n =>
SourceName ->
TableInfo b ->
SelPermInfo b ->
m (Parser 'Output n (AnnotatedFields b))
```
There are three reasons to change this.
1. We often pass a `Maybe (xPermInfo b)` instead of a proper `xPermInfo b`, and it's not clear what the intended semantics of this is. Some potential improvements on the data types involved are discussed in issue hasura/graphql-engine-mono#3125.
2. In most cases we also already pass a `TableInfo b`, and together with the `MonadRole` that is usually also in scope, this means that we could look up the required permissions regardless: so passing the permissions explicitly undermines the "single source of truth" principle. Breaking this principle also makes the code more difficult to read.
3. We are working towards role-based parsers (see hasura/graphql-engine-mono#2711), where the `{InputFields,Field,}Parser` objects are constructed in a role-invariant way, so that we have a single object that can be used for all roles. In particular, this means that the schema building blocks _need_ to be constructed in a role-invariant way. While this PR doesn't accomplish that, it does reduce the amount of role-specific arguments being passed, thus fixing hasura/graphql-engine-mono#3068.
Concretely, this PR simply drops the `xPermInfo b` argument from almost all schema building blocks. Instead these objects are looked up from the `TableInfo b` as-needed. The resulting code is considerably simpler and shorter.
One way to interpret this change is as follows. Before this PR, we figured out permissions at the top-level in `Hasura.GraphQL.Schema`, passing down the obtained `xPermInfo` objects as required. After this PR, we have a bottom-up approach where the schema building blocks themselves decide whether they want to be included for a particular role.
So this moves some permission logic out of `Hasura.GraphQL.Schema`, which is very complex.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3608
GitOrigin-RevId: 51a744f34ec7d57bc8077667ae7f9cb9c4f6c962
2022-02-17 11:16:20 +03:00
|
|
|
pure $ catMaybes [delete, deleteByPk]
|
2022-02-28 10:49:13 +03:00
|
|
|
where
|
|
|
|
deleteDesc = buildFieldDescription defaultDeleteDesc $ _crfComment _tcrfDelete
|
|
|
|
deletePKDesc = buildFieldDescription defaultDeletePKDesc $ _crfComment _tcrfDeleteByPk
|
|
|
|
defaultDeleteDesc = "delete data from the table: " <>> tableName
|
|
|
|
defaultDeletePKDesc = "delete single row from the table: " <>> tableName
|
|
|
|
TableCustomRootFields {..} = _tcCustomRootFields . _tciCustomConfig $ _tiCoreInfo tableInfo
|
2021-02-09 15:47:21 +03:00
|
|
|
|
2022-05-25 13:24:41 +03:00
|
|
|
buildFunctionQueryFieldsPG ::
|
|
|
|
forall r m n pgKind.
|
|
|
|
MonadBuildSchema ('Postgres pgKind) r m n =>
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
SourceInfo ('Postgres pgKind) ->
|
2022-05-25 13:24:41 +03:00
|
|
|
FunctionName ('Postgres pgKind) ->
|
|
|
|
FunctionInfo ('Postgres pgKind) ->
|
|
|
|
TableName ('Postgres pgKind) ->
|
|
|
|
m [FieldParser n (QueryDB ('Postgres pgKind) (RemoteRelationshipField UnpreparedValue) (UnpreparedValue ('Postgres pgKind)))]
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
buildFunctionQueryFieldsPG sourceInfo functionName functionInfo tableName = do
|
2021-11-26 00:07:53 +03:00
|
|
|
let -- select function
|
2021-09-24 12:18:40 +03:00
|
|
|
funcDesc =
|
|
|
|
Just . G.Description $
|
|
|
|
flip fromMaybe (_fiComment functionInfo) $ "execute function " <> functionName <<> " which returns " <>> tableName
|
2021-09-24 01:56:37 +03:00
|
|
|
-- select function agg
|
2021-10-07 16:02:19 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
funcAggDesc = Just $ G.Description $ "execute function " <> functionName <<> " and query aggregates on result of table type " <>> tableName
|
2021-10-07 16:02:19 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
queryResultType =
|
|
|
|
case _fiJsonAggSelect functionInfo of
|
|
|
|
JASMultipleRows -> QDBMultipleRows
|
|
|
|
JASSingleObject -> QDBSingleRow
|
2021-10-07 16:02:19 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
catMaybes
|
|
|
|
<$> sequenceA
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
[ optionalFieldParser (queryResultType) $ selectFunction sourceInfo functionInfo funcDesc,
|
|
|
|
optionalFieldParser (QDBAggregation) $ selectFunctionAggregate sourceInfo functionInfo funcAggDesc
|
2021-09-24 01:56:37 +03:00
|
|
|
]
|
2021-02-09 15:47:21 +03:00
|
|
|
|
2022-05-25 13:24:41 +03:00
|
|
|
buildFunctionMutationFieldsPG ::
|
|
|
|
forall r m n pgKind.
|
|
|
|
MonadBuildSchema ('Postgres pgKind) r m n =>
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
SourceInfo ('Postgres pgKind) ->
|
2022-05-25 13:24:41 +03:00
|
|
|
FunctionName ('Postgres pgKind) ->
|
|
|
|
FunctionInfo ('Postgres pgKind) ->
|
|
|
|
TableName ('Postgres pgKind) ->
|
|
|
|
m [FieldParser n (MutationDB ('Postgres pgKind) (RemoteRelationshipField UnpreparedValue) (UnpreparedValue ('Postgres pgKind)))]
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
buildFunctionMutationFieldsPG sourceInfo functionName functionInfo tableName = do
|
2021-11-26 00:07:53 +03:00
|
|
|
let funcDesc = Just $ G.Description $ "execute VOLATILE function " <> functionName <<> " which returns " <>> tableName
|
2021-09-24 01:56:37 +03:00
|
|
|
jsonAggSelect = _fiJsonAggSelect functionInfo
|
|
|
|
catMaybes
|
|
|
|
<$> sequenceA
|
Remove circular dependency in schema building code
### Description
The main goal of this PR is, as stated, to remove the circular dependency in the schema building code. This cycle arises from the existence of remote relationships: when we build the schema for a source A, a remote relationship might force us to jump to the schema of a source B, or some remote schema. As a result, we end up having to do a dispatch from a "leaf" of the schema, similar to the one done at the root. In turn, this forces us to carry along in the schema a lot of information required for that dispatch, AND it forces us to import the instances in scope, creating an import loop.
As discussed in #4489, this PR implements the "dependency injection" solution: we pass to the schema a function to call to do the dispatch, and to get a generated field for a remote relationship. That way, this function can be chosen at the root level, and the leaves need not be aware of the overall context.
This PR grew a bit bigger than that, however; in an attempt to try and remove the `SourceCache` from the schema altogether, it changed a lot of functions across the schema building code, to thread along the `SourceInfo b` of the source being built. This avoids having to do cache lookups within a given source. A few cases remain, such as relay, that we might try to tackle in a subsequent PR.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4557
GitOrigin-RevId: 9388e48372877520a72a9fd1677005df9f7b2d72
2022-05-27 20:21:22 +03:00
|
|
|
[ optionalFieldParser (MDBFunction jsonAggSelect) $ selectFunction sourceInfo functionInfo funcDesc
|
2021-02-09 15:47:21 +03:00
|
|
|
-- TODO: do we want aggregate mutation functions?
|
2021-09-24 01:56:37 +03:00
|
|
|
]
|
2022-02-28 10:49:13 +03:00
|
|
|
|
|
|
|
buildFieldDescription :: Text -> Comment -> Maybe G.Description
|
|
|
|
buildFieldDescription defaultDescription = \case
|
|
|
|
Automatic -> Just $ G.Description defaultDescription
|
|
|
|
Explicit comment -> G.Description . toTxt <$> comment
|