# Customising Function Root Field Names ## Metadata ``` --- authors: Vamshi Surabhi , Gil Mizrahi , Philip Lykke Carlsen discussion: https://github.com/hasura/graphql-engine/issues/7405 state: published --- ``` ## Description We should let users customise the GraphQL name that appears in the schema of SQL Functions tracked by The GraphQL Engine. ### Problem Being able to customise the names that _tables_ are exposed under in the GraphQL schema is already supported by The GraphQL Engine, since [PR-5719](https://github.com/hasura/graphql-engine/pull/5719). Citing the above PR for motivation: > .. The identifier can be useful to use when tracking tables with non GraphQL > compliant names or it can be useful if the nodes that are generated should not > be dependent on the table name i.e when a table is tracked with an identifier > and if the table has been renamed, the node names will not be changed due to > the identifier. The same motivation applies for functions that are exposed in the GraphQL Schema. ### Success This feature will have been implemented fully when a user is able to, through the Console: * Track a function * Set a custom name for it * Use that name in queries Similarly, the server-specific part will have been implemented once there is an integration test that performs the above steps via the server API. ## What The server part should add new metadata API endpoint(s) that allows a user, for some given tracked function, to specify a custom name that is used to query this function in the GraphQL Schema. In the absence of this feature, the name of the tracked function is just directly exposed in the GraphQL schema [[1]](#footnote-1-def). The console part should include a tab within the tracked function page that lets users specify the customised name, similarly to how custom table names are specified. It's important to note that this change only affects the GraphQL frontend, leaving SQL generation untouched. Also note that, since the Postgres backend is the only one that supports functions, the change described in this document is only in scope for Postgres. In the longer perspective, the query abstraction feature that will be implemented across backends will not be SQL Functions directly, but rather _Stored Queries_, which as of this writing have yet to be specified. (See [Slack Discussion](https://hasurahq.slack.com/archives/C01RZPEPF0W/p1627580823109200)) ### Effects and Interactions This will require work on both Server and Console. Once the Server parts have been implemented the Console parts can be implemented. This adds new metadata API endpoints. We don't perceive this feature to interfere with any other features. ### Unresolved Questions None. See linked discussion for historically unresolved questions. ## How Functions already have a `configuration` section - we can add two top level fields called `custom_name` and `custom_root_fields` as follows: ```yaml functions: - function: function_name configuration: session_argument: ... exposed_as: ... custom_name: a custom graphql compliant name custom_root_fields: function: FunctionName function_aggregate: SomeOtherName permissions: ... ``` Console will require an equivalent of `set_table_customization`, maybe `set_function_configuration` to allow modifying this configuration after a function is tracked. The `configuration` section of a function (`FunctionMetadata b`) needs two new options: 1. `custom_name` is optional and expects a GraphQL name. `custom_name` should be used in place of the function's name when generating GraphQL fields or types for the function. 1. `custom_root_fields` is optional and expects an object with the following two fields (both should be GraphQL names): 1. `function`: This name should be used for the field name which exposes the function in the appropriate operation root (query/mutation/subscription). When this not specified, we use `` as the field name. 1. `function_aggregate`: This name should be used for the aggregate operation that is exposed on the function. In the absence of this option, we use `_aggregate` as the field name. Note that both `function` and `function_name` take precedence over `custom_name`. We do not expect functions support on other databases, so this is only expected to work on Postgres databases. (These are equivalent to `custom_name` and `custom_root_fields` in a table's `configuration` section (`TableConfig b`)) In addition, we need an API for updating this configuration from the console. For table configuration, it is done through `set_table_customization` so we need something similar to that for functions too. I'm wondering if it makes sense to just add `replace_if_exists` key to `track_function` API instead of a `set_function_customization` API. ### Examples Example, consider the following schema: ```sql create table article ( id serial primary key, title text not null, content text not null ); create function search_articles(key text) returns setof articles AS $$ select * from article where title ilike ('%' || key || '%') OR content ilike ('%' || key || '%') $$ language sql stable; ``` with this metadata: ```yaml version: 3 sources: - name: wb kind: postgres configuration: connection_info: database_url: from_env: WORKBENCH_URL tables: - table: article functions: - function: search_articles ``` We would have the following fields in `query_root` (considering only the fields of function `search_articles`): ```graphql search_articles( args: search_articles_args! distinct_on: [article_select_column!] limit: Int offset: Int order_by: [article_order_by!] where: article_bool_exp ): [article!]! search_articles_aggregate( args: search_articles_args! distinct_on: [article_select_column!] limit: Int offset: Int order_by: [article_order_by!] where: article_bool_exp ): article_aggregate! ``` If `custom_name` is specified as follows: ```yaml version: 3 sources: - name: wb kind: postgres configuration: connection_info: database_url: from_env: WORKBENCH_URL tables: - table: article functions: - function: search_articles configuration: custom_name: find_articles ``` the root fields would have the following schema: ```graphql find_articles( args: find_articles_args! distinct_on: [article_select_column!] limit: Int offset: Int order_by: [article_order_by!] where: article_bool_exp ): [article!]! find_articles_aggregate( args: find_articles_args! distinct_on: [article_select_column!] limit: Int offset: Int order_by: [article_order_by!] where: article_bool_exp ): article_aggregate! ``` The changes are: ``` search_articles -> find_articles (root field name) search_articles_aggregate -> find_articles_aggregate (root field name) search_articles_args -> find_articles_args (input type name) ``` Instead, if `custom_root_fields` are specified as follows: ```yaml version: 3 sources: - name: wb kind: postgres configuration: connection_info: database_url: from_env: WORKBENCH_URL tables: - table: article functions: - function: search_articles configuration: custom_name: find_articles custom_root_fields: function: FindArticles function_aggregate: FindArticlesAggregate ``` the generated schema should be: ```graphql FindArticles( args: find_articles_args! distinct_on: [article_select_column!] limit: Int offset: Int order_by: [article_order_by!] where: article_bool_exp ): [article!]! FindArticlesAggregate( args: find_articles_args! distinct_on: [article_select_column!] limit: Int offset: Int order_by: [article_order_by!] where: article_bool_exp ): article_aggregate! ``` The changes are: ``` find_articles -> FindArticles (root field name) find_articles_aggregate -> FindArticlesAggregate (root field name) ``` ## Future work In general maybe we can extend most of our `track_*/create_*` APIs with `replace_if_exists` similar to `CREATE OR REPLACE` in Postgres? ## Footnotes [1][^](#footnote-1-ref): Subject to the name being made into a valid GraphQL identifier.