graphql-engine/rfcs/source-customization.md

101 lines
4.4 KiB
Markdown
Raw Normal View History

# Source Customization
See issue [#6974](https://github.com/hasura/graphql-engine/issues/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:
```yaml
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:
```haskell
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:
```haskell
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
```haskell
data RootFieldAlias = RootFieldAlias
{ _rfaNamespace :: !(Maybe G.Name),
_rfaAlias :: !G.Name
}
deriving (Show, Eq, Generic)
```
and the type alias
```haskell
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.