2021-11-10 03:37:42 +03:00
|
|
|
-- | This module defines the type class 'BackendSchema' and auxiliary types.
|
|
|
|
--
|
|
|
|
-- 'BackendSchema' represents the part of the interface that a backend driver
|
|
|
|
-- presents to the GraphQL Engine core that is responsible for generating
|
|
|
|
-- the backend's Schema Parsers.
|
|
|
|
--
|
|
|
|
-- The Schema Parsers recognise (and reflect) the schema that a backend exposes.
|
|
|
|
--
|
|
|
|
-- The 'BackendSchema' methods are used by
|
|
|
|
-- 'Hasura.GraphQL.Schema.buildGQLContext', which is the core's entrypoint to
|
|
|
|
-- schema generation.
|
|
|
|
--
|
|
|
|
-- Many of the 'BackendSchema' methods will have default implementations that a
|
|
|
|
-- backend driver may use. These may be found (chiefly) in the modules:
|
|
|
|
--
|
|
|
|
-- * The module "Hasura.GraphQL.Schema.Build", commonly qualified @GSB@
|
|
|
|
-- * "Hasura.GraphQL.Schema.Select", commonly qualified @GSS@
|
|
|
|
-- * "Hasura.GraphQL.Schema.BoolExp"
|
|
|
|
--
|
|
|
|
-- For more information see:
|
|
|
|
--
|
|
|
|
-- * <https://github.com/hasura/graphql-engine/blob/master/server/documentation/schema.md Technical overview of Schema Generation >
|
|
|
|
-- * The type 'Hasura.GraphQL.Parser.Parser', and associated source code notes
|
|
|
|
-- in the same folder (not exposed with Haddock unfortunately)
|
2021-11-04 19:08:33 +03:00
|
|
|
module Hasura.GraphQL.Schema.Backend
|
2021-11-08 21:11:44 +03:00
|
|
|
( -- * Main Types
|
|
|
|
BackendSchema (..),
|
2021-11-04 19:08:33 +03:00
|
|
|
MonadBuildSchema,
|
2021-11-08 21:11:44 +03:00
|
|
|
|
|
|
|
-- * Auxiliary Types
|
|
|
|
ComparisonExp,
|
2021-11-10 03:37:42 +03:00
|
|
|
|
|
|
|
-- * Note: @BackendSchema@ modelling principles
|
|
|
|
-- $modelling
|
2021-11-04 19:08:33 +03:00
|
|
|
)
|
|
|
|
where
|
2020-12-01 18:50:18 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
import Data.Has
|
|
|
|
import Hasura.Base.Error
|
|
|
|
import Hasura.GraphQL.Parser hiding (Type)
|
|
|
|
import Hasura.GraphQL.Schema.Common
|
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.IR
|
2021-10-01 15:52:19 +03:00
|
|
|
import Hasura.RQL.IR.Insert qualified as IR
|
2021-09-24 01:56:37 +03:00
|
|
|
import Hasura.RQL.IR.Select qualified as IR
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Backend
|
|
|
|
import Hasura.RQL.Types.Column hiding (EnumValueInfo)
|
|
|
|
import Hasura.RQL.Types.Common
|
|
|
|
import Hasura.RQL.Types.ComputedField
|
|
|
|
import Hasura.RQL.Types.Function
|
|
|
|
import Hasura.RQL.Types.Relationships.Local
|
|
|
|
import Hasura.RQL.Types.SchemaCache
|
|
|
|
import Hasura.RQL.Types.Table
|
|
|
|
import Hasura.SQL.Backend
|
2021-09-24 01:56:37 +03:00
|
|
|
import Language.GraphQL.Draft.Syntax qualified as G
|
2020-12-01 18:50:18 +03:00
|
|
|
|
2021-11-10 03:37:42 +03:00
|
|
|
-- TODO: Might it make sense to add those constraints to MonadSchema directly?
|
|
|
|
|
|
|
|
-- | Bag of constraints available to the methods of @BackendSchema@.
|
|
|
|
--
|
|
|
|
-- Note that @BackendSchema b@ is itself part of this, so a methods may also
|
|
|
|
-- call other methods. This might seem trivial, but it can be easy to miss when
|
|
|
|
-- the functions used to implement a class instance are defined in multiple
|
|
|
|
-- modules.
|
2021-02-03 19:17:20 +03:00
|
|
|
type MonadBuildSchema b r m n =
|
2021-12-22 02:14:56 +03:00
|
|
|
( BackendSchema b,
|
|
|
|
MonadBuildSchemaBase r m n
|
2021-02-03 19:17:20 +03:00
|
|
|
)
|
|
|
|
|
2021-11-10 03:37:42 +03:00
|
|
|
-- | This type class is responsible for generating the schema of a backend.
|
|
|
|
-- Its methods are called by the common core that orchestrates the various
|
|
|
|
-- backend drivers.
|
|
|
|
--
|
|
|
|
-- Its purpose in life is to make it convenient to express the GraphQL schema we
|
|
|
|
-- want to expose for the backends that we support. This means balancing the
|
|
|
|
-- desire to have consistency with the desire to differentiate the schema of a
|
|
|
|
-- backend.
|
|
|
|
--
|
|
|
|
-- This means that it is expected to evolve over time as we add new backends,
|
|
|
|
-- and that you have the license to change it: Whatever form it currently takes
|
|
|
|
-- only reflects status quo current implementation.
|
|
|
|
--
|
|
|
|
-- The module "Hasura.GraphQL.Schema.Build" (commonly qualified as @GSB@)
|
|
|
|
-- provides standard building blocks for implementing many methods of this
|
|
|
|
-- class. And as such, these two modules are very much expected to evolve in
|
|
|
|
-- tandem.
|
|
|
|
--
|
|
|
|
-- See <#modelling Note BackendSchema modelling principles>.
|
2022-02-03 19:13:50 +03:00
|
|
|
class
|
|
|
|
( Backend b,
|
|
|
|
Eq (BooleanOperators b (UnpreparedValue b))
|
|
|
|
) =>
|
|
|
|
BackendSchema (b :: BackendType)
|
|
|
|
where
|
2021-02-03 19:17:20 +03:00
|
|
|
-- top level parsers
|
2021-09-24 01:56:37 +03:00
|
|
|
buildTableQueryFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
|
|
|
G.Name ->
|
2021-12-07 16:12:02 +03:00
|
|
|
m [FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2022-04-22 22:53:12 +03:00
|
|
|
buildTableStreamingSubscriptionFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
|
|
|
G.Name ->
|
|
|
|
m [FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2021-09-24 01:56:37 +03:00
|
|
|
buildTableRelayQueryFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
|
|
|
G.Name ->
|
|
|
|
NESeq (ColumnInfo b) ->
|
2021-12-07 16:12:02 +03:00
|
|
|
m [FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2021-09-24 01:56:37 +03:00
|
|
|
buildTableInsertMutationFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
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 ->
|
2021-09-24 01:56:37 +03:00
|
|
|
SourceName ->
|
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
|
|
|
G.Name ->
|
2022-04-01 09:43:05 +03:00
|
|
|
m [FieldParser n (AnnotatedInsert b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2021-11-10 03:37:42 +03:00
|
|
|
|
|
|
|
-- | This method is responsible for building the GraphQL Schema for mutations
|
|
|
|
-- backed by @UPDATE@ statements on some table, as described in
|
|
|
|
-- @https://hasura.io/docs/latest/graphql/core/databases/postgres/mutations/update.html@.
|
|
|
|
--
|
|
|
|
-- The suggested way to implement this is using building blocks in GSB, c.f.
|
|
|
|
-- its namesake @GSB.@'Hasura.GraphQL.Schema.Build.buildTableUpdateMutationFields'.
|
2021-09-24 01:56:37 +03:00
|
|
|
buildTableUpdateMutationFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
2021-11-10 03:37:42 +03:00
|
|
|
-- | The source that the table lives in
|
2021-09-24 01:56:37 +03:00
|
|
|
SourceName ->
|
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
|
2021-09-24 01:56:37 +03:00
|
|
|
G.Name ->
|
2021-12-07 16:12:02 +03:00
|
|
|
m [FieldParser n (AnnotatedUpdateG b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2021-11-10 03:37:42 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
buildTableDeleteMutationFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
TableName b ->
|
|
|
|
TableInfo b ->
|
|
|
|
G.Name ->
|
2021-12-07 16:12:02 +03:00
|
|
|
m [FieldParser n (AnnDelG b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2021-11-26 00:07:53 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
buildFunctionQueryFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
FunctionName b ->
|
|
|
|
FunctionInfo b ->
|
|
|
|
TableName b ->
|
2021-12-07 16:12:02 +03:00
|
|
|
m [FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2021-11-26 00:07:53 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
buildFunctionRelayQueryFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
FunctionName b ->
|
|
|
|
FunctionInfo b ->
|
|
|
|
TableName b ->
|
|
|
|
NESeq (ColumnInfo b) ->
|
2021-12-07 16:12:02 +03:00
|
|
|
m [FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2021-11-26 00:07:53 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
buildFunctionMutationFields ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
FunctionName b ->
|
|
|
|
FunctionInfo b ->
|
|
|
|
TableName b ->
|
2021-12-07 16:12:02 +03:00
|
|
|
m [FieldParser n (MutationDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
2021-02-03 19:17:20 +03:00
|
|
|
|
2021-06-15 18:53:20 +03:00
|
|
|
-- table components
|
2021-09-24 01:56:37 +03:00
|
|
|
tableArguments ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
TableInfo b ->
|
|
|
|
m (InputFieldsParser n (IR.SelectArgsG b (UnpreparedValue b)))
|
2021-06-15 18:53:20 +03:00
|
|
|
|
2021-10-01 15:52:19 +03:00
|
|
|
-- | Make a parser for relationships. Default implementaton elides
|
|
|
|
-- relationships altogether.
|
|
|
|
mkRelationshipParser ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
RelInfo b ->
|
2022-04-01 09:43:05 +03:00
|
|
|
m (Maybe (InputFieldsParser n (Maybe (IR.AnnotatedInsertField b (UnpreparedValue b)))))
|
2021-10-01 15:52:19 +03:00
|
|
|
mkRelationshipParser _ _ = pure Nothing
|
|
|
|
|
2021-02-03 19:17:20 +03:00
|
|
|
-- backend extensions
|
2021-09-24 01:56:37 +03:00
|
|
|
relayExtension :: Maybe (XRelay b)
|
2021-06-09 16:02:15 +03:00
|
|
|
nodesAggExtension :: Maybe (XNodesAgg b)
|
2022-04-22 22:53:12 +03:00
|
|
|
streamSubscriptionExtension :: Maybe (XStreamingSubscription b)
|
2021-02-03 19:17:20 +03:00
|
|
|
|
|
|
|
-- individual components
|
2021-09-24 01:56:37 +03:00
|
|
|
columnParser ::
|
2021-10-29 17:42:07 +03:00
|
|
|
(MonadSchema n m, MonadError QErr m, MonadReader r m, Has MkTypename r) =>
|
2021-09-24 01:56:37 +03:00
|
|
|
ColumnType b ->
|
2021-12-20 20:02:32 +03:00
|
|
|
G.Nullability -> -- TODO maybe use Hasura.GraphQL.Parser.Schema.Nullability instead?
|
2021-09-24 01:56:37 +03:00
|
|
|
m (Parser 'Both n (ValueWithOrigin (ColumnValue b)))
|
|
|
|
|
2020-12-01 18:50:18 +03:00
|
|
|
-- | The "path" argument for json column fields
|
2021-09-24 01:56:37 +03:00
|
|
|
jsonPathArg ::
|
|
|
|
MonadParse n =>
|
|
|
|
ColumnType b ->
|
|
|
|
InputFieldsParser n (Maybe (IR.ColumnOp b))
|
|
|
|
|
|
|
|
orderByOperators ::
|
|
|
|
NonEmpty (Definition EnumValueInfo, (BasicOrderType b, NullsOrderType b))
|
|
|
|
comparisonExps ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
ColumnType b ->
|
|
|
|
m (Parser 'Input n [ComparisonExp b])
|
2021-11-08 21:11:44 +03:00
|
|
|
|
2022-01-18 17:53:44 +03:00
|
|
|
-- | The input fields parser, for "count" aggregate field, yielding a function
|
|
|
|
-- which generates @'CountType b' from optional "distinct" field value
|
|
|
|
countTypeInput ::
|
|
|
|
MonadParse n =>
|
|
|
|
Maybe (Parser 'Both n (Column b)) ->
|
|
|
|
InputFieldsParser n (CountDistinct -> CountType b)
|
2021-11-08 21:11:44 +03:00
|
|
|
|
2020-12-01 18:50:18 +03:00
|
|
|
aggregateOrderByCountType :: ScalarType b
|
2021-09-24 01:56:37 +03:00
|
|
|
|
2020-12-01 18:50:18 +03:00
|
|
|
-- | Computed field parser
|
2021-09-24 01:56:37 +03:00
|
|
|
computedField ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
SourceName ->
|
|
|
|
ComputedFieldInfo b ->
|
|
|
|
TableName 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
|
|
|
TableInfo b ->
|
2021-09-24 01:56:37 +03:00
|
|
|
m (Maybe (FieldParser n (AnnotatedField b)))
|
|
|
|
|
2020-12-01 18:50:18 +03:00
|
|
|
-- | The 'node' root field of a Relay request.
|
2021-09-24 01:56:37 +03:00
|
|
|
node ::
|
|
|
|
MonadBuildSchema b r m n =>
|
|
|
|
m (Parser 'Output n (HashMap (TableName b) (SourceName, SourceConfig b, SelPermInfo b, PrimaryKeyColumns b, AnnotatedFields b)))
|
2020-12-01 18:50:18 +03:00
|
|
|
|
|
|
|
type ComparisonExp b = OpExpG b (UnpreparedValue b)
|
2021-11-10 03:37:42 +03:00
|
|
|
|
|
|
|
-- $modelling
|
|
|
|
-- #modelling#
|
|
|
|
--
|
|
|
|
-- In its current form, we model every component, from the top level (query,
|
|
|
|
-- insert mutation, etc.) of the schema down to its leaf values, as a type class
|
|
|
|
-- method of @BackendSchema@.
|
|
|
|
--
|
|
|
|
-- Consider, for example, the following query for a given table "author":
|
|
|
|
--
|
|
|
|
-- > query {
|
|
|
|
-- > author(where: {id: {_eq: 2}}) {
|
|
|
|
-- > name
|
|
|
|
-- > }
|
|
|
|
-- > }
|
|
|
|
--
|
|
|
|
-- The chain of functions leading to a parser for this RootField will be along
|
|
|
|
-- the lines of:
|
|
|
|
--
|
|
|
|
-- > > BackendSchema.buildTableQueryFields (Suggested default its GSB namesake)
|
|
|
|
-- > > GSS.selectTable
|
|
|
|
-- > > BackendSchema.tableArguments (Suggested default implementation being
|
|
|
|
-- > GSS.defaultTableArgs)
|
|
|
|
-- > > GSS.tableWhereArg
|
|
|
|
-- > > GSBE.boolExp
|
|
|
|
-- > > BackendSchema.comparisonExps
|
|
|
|
-- > > BackendSchema.columnParser
|
|
|
|
-- >
|
|
|
|
-- > > tableSelectionSet (...)
|
|
|
|
-- > > fieldSelection
|
|
|
|
--
|
|
|
|
-- (where the abbreviation @GSB@ refers to "Hasura.GraphQL.Schema.Build" and @GSS@
|
|
|
|
-- refers to "Hasura.GraphQL.Schema.Select", and @GSBE@ refers to
|
|
|
|
-- "Hasura.GraphQL.Schema.BoolExp".)
|
|
|
|
--
|
|
|
|
-- Several of those steps are part of the class, meaning that a backend can
|
|
|
|
-- customize part of this tree without having to reimplement all of it. For
|
|
|
|
-- instance, a backend that supports a different set ot table arguments can
|
|
|
|
-- choose to reimplement 'tableArguments', but can still use
|
|
|
|
-- 'Hasura.GraphQL.Schema.Select.tableWhereArg' in its custom implementation.
|
|
|
|
--
|
|
|
|
-- Applying the above modelling guidelines has pros and cons:
|
|
|
|
--
|
|
|
|
-- * Pro: You can specify both shared and diverging behavior.
|
|
|
|
-- * Pro: You can specify a lot of behavior implicitly, i.e. it's easy to write.
|
|
|
|
-- * Con: You can specify a lot of behavior implicitly, i.e. it's hard do
|
|
|
|
-- understand without tracing through implementations.
|
|
|
|
-- * Con: You get a proliferation of type class methods and it's difficult to
|
|
|
|
-- understand how they fit together.
|
|
|
|
--
|
|
|
|
-- == Going forward, we want to follow some different modelling guidelines:
|
|
|
|
--
|
|
|
|
-- We should break up / refactor the building blocks (in
|
|
|
|
-- "Hasura.GraphQL.Schema.Build" etc.) which are used to implement the top-level
|
|
|
|
-- type class methods (e.g. @BackendSchema@.'buildTableQueryFields', c.f.
|
|
|
|
-- @GSB.@'Hasura.GraphQL.Schema.Build.buildTableQueryFields', etc.) and have them
|
|
|
|
-- invoke the backend-specific behaviors they rely on via /function arguments/
|
|
|
|
-- instead of other type class methods.
|
|
|
|
--
|
|
|
|
-- When we do this, the function call sites (which will often be in @instance
|
|
|
|
-- BackendSchema ...@) becomes the centralised place where we decide which behavior
|
|
|
|
-- variation to follow.
|
|
|
|
--
|
|
|
|
-- When faced with answering the question of "what does this method do, and how does
|
|
|
|
-- it do it?", at least you will have listed the other components it depends on
|
|
|
|
-- front and center without having to trace through its implementation.
|
|
|
|
--
|
|
|
|
-- That is of course, if we refactor our building blocks mindfully into
|
|
|
|
-- conceptually meaningful units. Otherwise we'll just end up with an
|
|
|
|
-- incomprehensible mass of poorly shaped pieces. And we will still have a hard
|
|
|
|
-- time explaining what they do.
|
|
|
|
--
|
|
|
|
-- In other words, It is still the case that if you don't clean your room
|
|
|
|
-- you'll be living in a mess.
|