graphql-engine/rfcs/source-customization.md
David Overton aac64f2c81 Source typename customization (close graphql-engine#6974)
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/1616
GitOrigin-RevId: f7eefd2367929209aa77895ea585e96a99a78d47
2021-10-29 14:43:14 +00:00

4.4 KiB

Source Customization

See issue #6974.

When adding multiple remote database sources there is a possibility of getting name conflicts if two sources have tables or functions with the same name. This can be worked around for individual tables by adding a custom_name and/or custom_root_fields, but it would be better to have source-level customization options to avoid conflicts.

Spec

Add an optional customization object to the *_add_source API:

customization:
  root_fields:
    namespace: "something"
    prefix: some_prefix
    suffix: some_suffix
  type_names:
    prefix: some_prefix
    suffix: some_suffix

All fields within the customization object are optional.

  • root_fields provides options for avoiding conflicts in root field names between sources.
    • If namespace is given then all of the source's root fields will be nested under a new namespace root field.
    • If prefix and/or suffix are given then they will be applied to the root field names generated for the source.
  • type_names provides options for avoiding conflicts in type names generated for a source.
    • If prefix and/or suffix are given then they will be applied to the type names generated for the source.
    • Prefix and suffix only affect types generated specifically for a source. They do not affect builtin scalar types (Int, Float, etc) or types starting with __.

Implementation approach

API and metadata changes

New data type SourceCustomization to be added to AddSource (for API) and SourceInfo (for metadata) to represent the customization options described above.

Type name customization

Type name customizations are applied by passing a customization function into the parser builders and applying it wherever we generate a type name. We add new type definitions:

newtype Typename = Typename {unTypename :: Name} deriving (Eq, Ord, Show, HasName, J.ToJSON)

type MkTypename = Name -> Typename

withTypenameCustomization :: forall m r a. (MonadReader r m, Has MkTypename r) => MkTypename -> m a -> m a

and add Has MkTypename r constraint to MonadBuildSchema to allow us to pass the typename customization function through the schema building code and insert customizations for sources where appropriate.

Remote source relationships

To allow type name customization in remote source relationships, we add the SourceTypeCustomization for the remote source to RemoteSourceRelationshipInfo. This allows us to call withTypenameCustomization when building the remote source schema.

Relay Node type

The Relay Node interface type is generated separately from the other query parsers. We need to make sure type name customizations are applied here too.

Root field name customization

Similar to type name customizations, we add new type definitions:

type MkRootFieldName = Name -> Name

withRootFieldNameCustomization :: forall m r a. (MonadReader r m, Has MkRootFieldName r) => MkRootFieldName -> m a -> m a

and add Has MkRootFieldName r constraint to MonadBuildSchema to allow us to apss the root field name customization function through to places where root field names are constructed.

Namespaces

The collection of root fields is represented in many places using the type InsOrdHashMap Name a where Name is the GraphQL name of a root field and a is whatever data we need associated with that field, e.g. the QueryRootField or ExecutionStep. To represent that root fields can now optionally have a namespace, we introduce the new data type

data RootFieldAlias = RootFieldAlias
  { _rfaNamespace :: !(Maybe G.Name),
    _rfaAlias :: !G.Name
  }
  deriving (Show, Eq, Generic)

and the type alias

type RootFieldMap = InsOrdHashMap RootFieldAlias

RootFieldMap a will now be used instead of InsOrdHashMap Name a where appropriate.

Subscriptions

JSON result objects for subscriptions are generated by the individual database backends (currently Postgres and MSSQL support subcriptions). If the subscription source has a namespace then we need to wrap the response from the database in an object with the namespace field before sending it to the websocket.

Tech debt

We previously implemented namespaces for remote schemas before the more general RootFieldAlias field was added. Now that we have RootFieldAlias we should use this for remote schema namespaces too.