graphql-engine/server/src-lib/Hasura/Server/API/Metadata.hs

612 lines
24 KiB
Haskell
Raw Normal View History

-- | The RQL metadata query ('/v1/metadata')
module Hasura.Server.API.Metadata where
import Hasura.Prelude
import qualified Data.Environment as Env
import qualified Network.HTTP.Client.Extended as HTTP
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Monad.Unique
import Data.Aeson
import Data.Aeson.Casing
import Data.Aeson.TH
import qualified Hasura.Tracing as Tracing
import Hasura.Base.Error
import Hasura.EncJSON
import Hasura.Metadata.Class
import Hasura.RQL.DDL.Action
server: multitenant metadata storage The metadata storage implementation for graphql-engine-multitenant. - It uses a centralized PG database to store metadata of all tenants (instead of per tenant database) - Similarly, it uses a single schema-sync listener thread per MT worker (instead of listener thread per tenant) (PS: although, the processor thread is spawned per tenant) - 2 new flags are introduced - `--metadataDatabaseUrl` and (optional) `--metadataDatabaseRetries` Internally, a "metadata mode" is introduced to indicate an external/managed store vs a store managed by each pro-server. To run : - obtain the schema file (located at `pro/server/res/cloud/metadata_db_schema.sql`) - apply the schema on a PG database - set the `--metadataDatabaseUrl` flag to point to the above database - run the MT executable The schema (and its migrations) for the metadata db is managed outside the MT worker. ### New metadata The following is the new portion of `Metadata` added : ```yaml version: 3 metrics_config: analyze_query_variables: true analyze_response_body: false api_limits: disabled: false depth_limit: global: 5 per_role: user: 7 editor: 9 rate_limit: per_role: user: unique_params: - x-hasura-user-id - x-hasura-team-id max_reqs_per_min: 20 global: unique_params: IP max_reqs_per_min: 10 ``` - In Pro, the code around fetching/updating/syncing pro-config is removed - That also means, `hdb_pro_catalog` for keeping the config cache is not required. Hence the `hdb_pro_catalog` is also removed - The required config comes from metadata / schema cache ### New Metadata APIs - `set_api_limits` - `remove_api_limits` - `set_metrics_config` - `remove_metrics_config` #### `set_api_limits` ```yaml type: set_api_limits args: disabled: false depth_limit: global: 5 per_role: user: 7 editor: 9 rate_limit: per_role: anonymous: max_reqs_per_min: 10 unique_params: "ip" editor: max_reqs_per_min: 30 unique_params: - x-hasura-user-id user: unique_params: - x-hasura-user-id - x-hasura-team-id max_reqs_per_min: 20 global: unique_params: IP max_reqs_per_min: 10 ``` #### `remove_api_limits` ```yaml type: remove_api_limits args: {} ``` #### `set_metrics_config` ```yaml type: set_metrics_config args: analyze_query_variables: true analyze_response_body: false ``` #### `remove_metrics_config` ```yaml type: remove_metrics_config args: {} ``` #### TODO - [x] on-prem pro implementation for `MonadMetadataStorage` - [x] move the project config from Lux to pro metadata (PR: #379) - [ ] console changes for pro config/api limits, subscription workers (cc @soorajshankar @beerose) - [x] address other minor TODOs - [x] TxIso for `MonadSourceResolver` - [x] enable EKG connection pool metrics - [x] add logging of connection info when sources are added? - [x] confirm if the `buildReason` for schema cache is correct - [ ] testing - [x] 1.3 -> 1.4 cloud migration script (#465; PR: #508) - [x] one-time migration of existing metadata from users' db to centralized PG - [x] one-time migration of pro project config + api limits + regression tests from metrics API to metadata - [ ] integrate with infra team (WIP - cc @hgiasac) - [x] benchmark with 1000+ tenants + each tenant making read/update metadata query every second (PR: https://github.com/hasura/graphql-engine-mono/pull/411) - [ ] benchmark with few tenants having large metadata (100+ tables etc.) - [ ] when user moves regions (https://github.com/hasura/lux/issues/1717) - [ ] metadata has to be migrated from one regional PG to another - [ ] migrate metrics data as well ? - [ ] operation logs - [ ] regression test runs - [ ] find a way to share the schema files with the infra team Co-authored-by: Naveen Naidu <30195193+Naveenaidu@users.noreply.github.com> GitOrigin-RevId: 39e8361f2c0e96e0f9e8f8fb45e6cc14857f31f1
2021-02-11 20:54:25 +03:00
import Hasura.RQL.DDL.ApiLimit
import Hasura.RQL.DDL.ComputedField
import Hasura.RQL.DDL.CustomTypes
import Hasura.RQL.DDL.Endpoint
import Hasura.RQL.DDL.EventTrigger
import Hasura.RQL.DDL.GraphqlSchemaIntrospection
[Preview] Inherited roles for postgres read queries fixes #3868 docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de` Note: To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`. Introduction ------------ This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`. How are select permissions of different roles are combined? ------------------------------------------------------------ A select permission includes 5 things: 1. Columns accessible to the role 2. Row selection filter 3. Limit 4. Allow aggregation 5. Scalar computed fields accessible to the role Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`. Let's say the following GraphQL query is queried with the `combined_roles` role. ```graphql query { employees { address phone } } ``` This will translate to the following SQL query: ```sql select (case when (P1 or P2) then address else null end) as address, (case when P2 then phone else null end) as phone from employee where (P1 or P2) ``` The other parameters of the select permission will be combined in the following manner: 1. Limit - Minimum of the limits will be the limit of the inherited role 2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation 3. Scalar computed fields - same as table column fields, as in the above example APIs for inherited roles: ---------------------- 1. `add_inherited_role` `add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments `role_name`: the name of the inherited role to be added (String) `role_set`: list of roles that need to be combined (Array of Strings) Example: ```json { "type": "add_inherited_role", "args": { "role_name":"combined_user", "role_set":[ "user", "user1" ] } } ``` After adding the inherited role, the inherited role can be used like single roles like earlier Note: An inherited role can only be created with non-inherited/singular roles. 2. `drop_inherited_role` The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument: `role_name`: name of the inherited role to be dropped Example: ```json { "type": "drop_inherited_role", "args": { "role_name":"combined_user" } } ``` Metadata --------- The derived roles metadata will be included under the `experimental_features` key while exporting the metadata. ```json { "experimental_features": { "derived_roles": [ { "role_name": "manager_is_employee_too", "role_set": [ "employee", "manager" ] } ] } } ``` Scope ------ Only postgres queries and subscriptions are supported in this PR. Important points: ----------------- 1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done. TODOs ------- - [ ] Tests - [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features - [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?) - [ ] Introspection test with a inherited role (nullability changes in a inherited role) - [ ] Docs - [ ] Changelog Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com> GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
import Hasura.RQL.DDL.InheritedRoles
import Hasura.RQL.DDL.Metadata
import Hasura.RQL.DDL.Permission
import Hasura.RQL.DDL.QueryCollection
import Hasura.RQL.DDL.Relationship
import Hasura.RQL.DDL.Relationship.Rename
import Hasura.RQL.DDL.RemoteRelationship
import Hasura.RQL.DDL.RemoteSchema
import Hasura.RQL.DDL.ScheduledTrigger
import Hasura.RQL.DDL.Schema
import Hasura.RQL.DDL.Schema.Source
import Hasura.RQL.Types
import Hasura.RQL.Types.Run
import Hasura.Server.Types (InstanceId (..), MaintenanceMode (..))
import Hasura.Server.Utils (APIVersion (..))
import Hasura.Server.Version (HasVersion)
import Hasura.Session
data RQLMetadataV1
= RMPgAddSource !(AddSource ('Postgres 'Vanilla))
| RMPgDropSource !DropSource
| RMPgTrackTable !(TrackTableV2 ('Postgres 'Vanilla))
| RMPgUntrackTable !(UntrackTable ('Postgres 'Vanilla))
| RMPgSetTableIsEnum !SetTableIsEnum
| RMPgSetTableCustomization !(SetTableCustomization ('Postgres 'Vanilla))
-- Postgres functions
| RMPgTrackFunction !(TrackFunctionV2 ('Postgres 'Vanilla))
| RMPgUntrackFunction !(UnTrackFunction ('Postgres 'Vanilla))
-- Postgres function permissions
| RMPgCreateFunctionPermission !(CreateFunctionPermission ('Postgres 'Vanilla))
| RMPgDropFunctionPermission !(DropFunctionPermission ('Postgres 'Vanilla))
-- Postgres table relationships
| RMPgCreateObjectRelationship !(CreateObjRel ('Postgres 'Vanilla))
| RMPgCreateArrayRelationship !(CreateArrRel ('Postgres 'Vanilla))
| RMPgDropRelationship !(DropRel ('Postgres 'Vanilla))
| RMPgSetRelationshipComment !(SetRelComment ('Postgres 'Vanilla))
| RMPgRenameRelationship !(RenameRel ('Postgres 'Vanilla))
-- Postgres computed fields
| RMPgAddComputedField !(AddComputedField ('Postgres 'Vanilla))
| RMPgDropComputedField !(DropComputedField ('Postgres 'Vanilla))
-- Postgres tables remote relationships
| RMPgCreateRemoteRelationship !(RemoteRelationship ('Postgres 'Vanilla))
| RMPgUpdateRemoteRelationship !(RemoteRelationship ('Postgres 'Vanilla))
| RMPgDeleteRemoteRelationship !(DeleteRemoteRelationship ('Postgres 'Vanilla))
-- Postgres tables permissions
| RMPgCreateInsertPermission !(CreatePerm InsPerm ('Postgres 'Vanilla))
| RMPgCreateSelectPermission !(CreatePerm SelPerm ('Postgres 'Vanilla))
| RMPgCreateUpdatePermission !(CreatePerm UpdPerm ('Postgres 'Vanilla))
| RMPgCreateDeletePermission !(CreatePerm DelPerm ('Postgres 'Vanilla))
| RMPgDropInsertPermission !(DropPerm InsPerm ('Postgres 'Vanilla))
| RMPgDropSelectPermission !(DropPerm SelPerm ('Postgres 'Vanilla))
| RMPgDropUpdatePermission !(DropPerm UpdPerm ('Postgres 'Vanilla))
| RMPgDropDeletePermission !(DropPerm DelPerm ('Postgres 'Vanilla))
| RMPgSetPermissionComment !(SetPermComment ('Postgres 'Vanilla))
-- Postgres tables event triggers
| RMPgCreateEventTrigger !(CreateEventTriggerQuery ('Postgres 'Vanilla))
| RMPgDeleteEventTrigger !(DeleteEventTriggerQuery ('Postgres 'Vanilla))
| RMPgRedeliverEvent !(RedeliverEventQuery ('Postgres 'Vanilla))
| RMPgInvokeEventTrigger !(InvokeEventTriggerQuery ('Postgres 'Vanilla))
-- MSSQL sources
| RMMssqlAddSource !(AddSource 'MSSQL)
| RMMssqlDropSource !DropSource
| RMMssqlTrackTable !(TrackTableV2 'MSSQL)
| RMMssqlUntrackTable !(UntrackTable 'MSSQL)
| RMMssqlSetTableCustomization !(SetTableCustomization 'MSSQL)
| RMMssqlCreateObjectRelationship !(CreateObjRel 'MSSQL)
| RMMssqlCreateArrayRelationship !(CreateArrRel 'MSSQL)
| RMMssqlDropRelationship !(DropRel 'MSSQL)
| RMMssqlSetRelationshipComment !(SetRelComment 'MSSQL)
| RMMssqlRenameRelationship !(RenameRel 'MSSQL)
| RMMssqlCreateInsertPermission !(CreatePerm InsPerm 'MSSQL)
| RMMssqlCreateSelectPermission !(CreatePerm SelPerm 'MSSQL)
| RMMssqlCreateUpdatePermission !(CreatePerm UpdPerm 'MSSQL)
| RMMssqlCreateDeletePermission !(CreatePerm DelPerm 'MSSQL)
| RMMssqlDropInsertPermission !(DropPerm InsPerm 'MSSQL)
| RMMssqlDropSelectPermission !(DropPerm SelPerm 'MSSQL)
| RMMssqlDropUpdatePermission !(DropPerm UpdPerm 'MSSQL)
| RMMssqlDropDeletePermission !(DropPerm DelPerm 'MSSQL)
| RMMssqlSetPermissionComment !(SetPermComment 'MSSQL)
-- Citus functions
| RMCitusTrackFunction !(TrackFunctionV2 ('Postgres 'Citus))
| RMCitusUntrackFunction !(UnTrackFunction ('Postgres 'Citus))
-- Citus function permissions
| RMCitusCreateFunctionPermission !(CreateFunctionPermission ('Postgres 'Citus))
| RMCitusDropFunctionPermission !(DropFunctionPermission ('Postgres 'Citus))
-- Citus sources
| RMCitusAddSource !(AddSource ('Postgres 'Citus))
| RMCitusDropSource !DropSource
| RMCitusTrackTable !(TrackTableV2 ('Postgres 'Citus))
| RMCitusUntrackTable !(UntrackTable ('Postgres 'Citus))
| RMCitusSetTableCustomization !(SetTableCustomization ('Postgres 'Citus))
-- Citus relationship
| RMCitusCreateObjectRelationship !(CreateObjRel ('Postgres 'Citus))
| RMCitusCreateArrayRelationship !(CreateArrRel ('Postgres 'Citus))
| RMCitusDropRelationship !(DropRel ('Postgres 'Citus))
| RMCitusSetRelationshipComment !(SetRelComment ('Postgres 'Citus))
| RMCitusRenameRelationship !(RenameRel ('Postgres 'Citus))
-- Citus permissions
| RMCitusCreateInsertPermission !(CreatePerm InsPerm ('Postgres 'Citus))
| RMCitusCreateSelectPermission !(CreatePerm SelPerm ('Postgres 'Citus))
| RMCitusCreateUpdatePermission !(CreatePerm UpdPerm ('Postgres 'Citus))
| RMCitusCreateDeletePermission !(CreatePerm DelPerm ('Postgres 'Citus))
| RMCitusDropInsertPermission !(DropPerm InsPerm ('Postgres 'Citus))
| RMCitusDropSelectPermission !(DropPerm SelPerm ('Postgres 'Citus))
| RMCitusDropUpdatePermission !(DropPerm UpdPerm ('Postgres 'Citus))
| RMCitusDropDeletePermission !(DropPerm DelPerm ('Postgres 'Citus))
| RMCitusSetPermissionComment !(SetPermComment ('Postgres 'Citus))
-- BigQuery sources
| RMBigqueryAddSource !(AddSource 'BigQuery)
| RMBigqueryDropSource !DropSource
| RMBigqueryTrackTable !(TrackTableV2 'BigQuery)
| RMBigquerySetTableCustomization !(SetTableCustomization 'BigQuery)
| RMBigqueryUntrackTable !(UntrackTable 'BigQuery)
| RMBigqueryCreateObjectRelationship !(CreateObjRel 'BigQuery)
| RMBigqueryCreateArrayRelationship !(CreateArrRel 'BigQuery)
| RMBigqueryDropRelationship !(DropRel 'BigQuery)
| RMBigquerySetRelationshipComment !(SetRelComment 'BigQuery)
| RMBigqueryRenameRelationship !(RenameRel 'BigQuery)
| RMBigqueryCreateInsertPermission !(CreatePerm InsPerm 'BigQuery)
| RMBigqueryCreateSelectPermission !(CreatePerm SelPerm 'BigQuery)
| RMBigqueryCreateUpdatePermission !(CreatePerm UpdPerm 'BigQuery)
| RMBigqueryCreateDeletePermission !(CreatePerm DelPerm 'BigQuery)
| RMBigqueryDropInsertPermission !(DropPerm InsPerm 'BigQuery)
| RMBigqueryDropSelectPermission !(DropPerm SelPerm 'BigQuery)
| RMBigqueryDropUpdatePermission !(DropPerm UpdPerm 'BigQuery)
| RMBigqueryDropDeletePermission !(DropPerm DelPerm 'BigQuery)
| RMBigquerySetPermissionComment !(SetPermComment 'BigQuery)
| RMRenameSource !RenameSource
-- Inconsistent metadata
| RMGetInconsistentMetadata !GetInconsistentMetadata
| RMDropInconsistentMetadata !DropInconsistentMetadata
-- Remote schemas
| RMAddRemoteSchema !AddRemoteSchemaQuery
| RMUpdateRemoteSchema !AddRemoteSchemaQuery
| RMRemoveRemoteSchema !RemoteSchemaNameQuery
| RMReloadRemoteSchema !RemoteSchemaNameQuery
| RMIntrospectRemoteSchema !RemoteSchemaNameQuery
-- remote-schema permissions
| RMAddRemoteSchemaPermissions !AddRemoteSchemaPermissions
| RMDropRemoteSchemaPermissions !DropRemoteSchemaPermissions
-- scheduled triggers
| RMCreateCronTrigger !CreateCronTrigger
| RMDeleteCronTrigger !ScheduledTriggerName
| RMCreateScheduledEvent !CreateScheduledEvent
| RMDeleteScheduledEvent !DeleteScheduledEvent
| RMGetScheduledEvents !GetScheduledEvents
| RMGetEventInvocations !GetEventInvocations
-- query collections, allow list related
| RMCreateQueryCollection !CreateCollection
| RMDropQueryCollection !DropCollection
| RMAddQueryToCollection !AddQueryToCollection
| RMDropQueryFromCollection !DropQueryFromCollection
| RMAddCollectionToAllowlist !CollectionReq
| RMDropCollectionFromAllowlist !CollectionReq
-- basic metadata management
| RMReplaceMetadata !ReplaceMetadata
| RMExportMetadata !ExportMetadata
| RMClearMetadata !ClearMetadata
| RMReloadMetadata !ReloadMetadata
-- actions
| RMCreateAction !CreateAction
| RMDropAction !DropAction
| RMUpdateAction !UpdateAction
| RMCreateActionPermission !CreateActionPermission
| RMDropActionPermission !DropActionPermission
| RMCreateRestEndpoint !CreateEndpoint
| RMDropRestEndpoint !DropEndpoint
| RMSetCustomTypes !CustomTypes
| RMDumpInternalState !DumpInternalState
| RMGetCatalogState !GetCatalogState
| RMSetCatalogState !SetCatalogState
server: multitenant metadata storage The metadata storage implementation for graphql-engine-multitenant. - It uses a centralized PG database to store metadata of all tenants (instead of per tenant database) - Similarly, it uses a single schema-sync listener thread per MT worker (instead of listener thread per tenant) (PS: although, the processor thread is spawned per tenant) - 2 new flags are introduced - `--metadataDatabaseUrl` and (optional) `--metadataDatabaseRetries` Internally, a "metadata mode" is introduced to indicate an external/managed store vs a store managed by each pro-server. To run : - obtain the schema file (located at `pro/server/res/cloud/metadata_db_schema.sql`) - apply the schema on a PG database - set the `--metadataDatabaseUrl` flag to point to the above database - run the MT executable The schema (and its migrations) for the metadata db is managed outside the MT worker. ### New metadata The following is the new portion of `Metadata` added : ```yaml version: 3 metrics_config: analyze_query_variables: true analyze_response_body: false api_limits: disabled: false depth_limit: global: 5 per_role: user: 7 editor: 9 rate_limit: per_role: user: unique_params: - x-hasura-user-id - x-hasura-team-id max_reqs_per_min: 20 global: unique_params: IP max_reqs_per_min: 10 ``` - In Pro, the code around fetching/updating/syncing pro-config is removed - That also means, `hdb_pro_catalog` for keeping the config cache is not required. Hence the `hdb_pro_catalog` is also removed - The required config comes from metadata / schema cache ### New Metadata APIs - `set_api_limits` - `remove_api_limits` - `set_metrics_config` - `remove_metrics_config` #### `set_api_limits` ```yaml type: set_api_limits args: disabled: false depth_limit: global: 5 per_role: user: 7 editor: 9 rate_limit: per_role: anonymous: max_reqs_per_min: 10 unique_params: "ip" editor: max_reqs_per_min: 30 unique_params: - x-hasura-user-id user: unique_params: - x-hasura-user-id - x-hasura-team-id max_reqs_per_min: 20 global: unique_params: IP max_reqs_per_min: 10 ``` #### `remove_api_limits` ```yaml type: remove_api_limits args: {} ``` #### `set_metrics_config` ```yaml type: set_metrics_config args: analyze_query_variables: true analyze_response_body: false ``` #### `remove_metrics_config` ```yaml type: remove_metrics_config args: {} ``` #### TODO - [x] on-prem pro implementation for `MonadMetadataStorage` - [x] move the project config from Lux to pro metadata (PR: #379) - [ ] console changes for pro config/api limits, subscription workers (cc @soorajshankar @beerose) - [x] address other minor TODOs - [x] TxIso for `MonadSourceResolver` - [x] enable EKG connection pool metrics - [x] add logging of connection info when sources are added? - [x] confirm if the `buildReason` for schema cache is correct - [ ] testing - [x] 1.3 -> 1.4 cloud migration script (#465; PR: #508) - [x] one-time migration of existing metadata from users' db to centralized PG - [x] one-time migration of pro project config + api limits + regression tests from metrics API to metadata - [ ] integrate with infra team (WIP - cc @hgiasac) - [x] benchmark with 1000+ tenants + each tenant making read/update metadata query every second (PR: https://github.com/hasura/graphql-engine-mono/pull/411) - [ ] benchmark with few tenants having large metadata (100+ tables etc.) - [ ] when user moves regions (https://github.com/hasura/lux/issues/1717) - [ ] metadata has to be migrated from one regional PG to another - [ ] migrate metrics data as well ? - [ ] operation logs - [ ] regression test runs - [ ] find a way to share the schema files with the infra team Co-authored-by: Naveen Naidu <30195193+Naveenaidu@users.noreply.github.com> GitOrigin-RevId: 39e8361f2c0e96e0f9e8f8fb45e6cc14857f31f1
2021-02-11 20:54:25 +03:00
-- 'ApiLimit' related
| RMSetApiLimits !ApiLimit
| RMRemoveApiLimits
-- 'MetricsConfig' related
| RMSetMetricsConfig !MetricsConfig
| RMRemoveMetricsConfig
[Preview] Inherited roles for postgres read queries fixes #3868 docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de` Note: To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`. Introduction ------------ This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`. How are select permissions of different roles are combined? ------------------------------------------------------------ A select permission includes 5 things: 1. Columns accessible to the role 2. Row selection filter 3. Limit 4. Allow aggregation 5. Scalar computed fields accessible to the role Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`. Let's say the following GraphQL query is queried with the `combined_roles` role. ```graphql query { employees { address phone } } ``` This will translate to the following SQL query: ```sql select (case when (P1 or P2) then address else null end) as address, (case when P2 then phone else null end) as phone from employee where (P1 or P2) ``` The other parameters of the select permission will be combined in the following manner: 1. Limit - Minimum of the limits will be the limit of the inherited role 2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation 3. Scalar computed fields - same as table column fields, as in the above example APIs for inherited roles: ---------------------- 1. `add_inherited_role` `add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments `role_name`: the name of the inherited role to be added (String) `role_set`: list of roles that need to be combined (Array of Strings) Example: ```json { "type": "add_inherited_role", "args": { "role_name":"combined_user", "role_set":[ "user", "user1" ] } } ``` After adding the inherited role, the inherited role can be used like single roles like earlier Note: An inherited role can only be created with non-inherited/singular roles. 2. `drop_inherited_role` The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument: `role_name`: name of the inherited role to be dropped Example: ```json { "type": "drop_inherited_role", "args": { "role_name":"combined_user" } } ``` Metadata --------- The derived roles metadata will be included under the `experimental_features` key while exporting the metadata. ```json { "experimental_features": { "derived_roles": [ { "role_name": "manager_is_employee_too", "role_set": [ "employee", "manager" ] } ] } } ``` Scope ------ Only postgres queries and subscriptions are supported in this PR. Important points: ----------------- 1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done. TODOs ------- - [ ] Tests - [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features - [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?) - [ ] Introspection test with a inherited role (nullability changes in a inherited role) - [ ] Docs - [ ] Changelog Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com> GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
-- inherited roles
| RMAddInheritedRole !AddInheritedRole
| RMDropInheritedRole !DropInheritedRole
| RMSetGraphqlSchemaIntrospectionOptions !SetGraphqlIntrospectionOptions
-- bulk metadata queries
| RMBulk [RQLMetadataRequest]
deriving (Eq)
data RQLMetadataV2
= RMV2ReplaceMetadata !ReplaceMetadataV2
| RMV2ExportMetadata !ExportMetadata
deriving (Eq)
data RQLMetadataRequest
= RMV1 !RQLMetadataV1
| RMV2 !RQLMetadataV2
deriving (Eq)
instance FromJSON RQLMetadataRequest where
parseJSON = withObject "RQLMetadataRequest" $ \o -> do
version <- o .:? "version" .!= VIVersion1
let val = Object o
case version of
VIVersion1 -> RMV1 <$> parseJSON val
VIVersion2 -> RMV2 <$> parseJSON val
instance ToJSON RQLMetadataRequest where
toJSON = \case
RMV1 q -> embedVersion VIVersion1 $ toJSON q
RMV2 q -> embedVersion VIVersion2 $ toJSON q
where
embedVersion version (Object o) =
Object $ o <> "version" .= version
-- never happens since JSON value of RQL queries are always objects
embedVersion _ _ = error "Unexpected: toJSON of RQLMetadtaV is not an object"
data RQLMetadata
= RQLMetadata
{ _rqlMetadataResourceVersion :: !(Maybe MetadataResourceVersion)
, _rqlMetadata :: !RQLMetadataRequest
} deriving (Eq)
instance FromJSON RQLMetadata where
parseJSON = withObject "RQLMetadata" $ \o -> do
_rqlMetadataResourceVersion <- o .:? "resource_version"
_rqlMetadata <- parseJSON $ Object o
pure RQLMetadata{..}
instance ToJSON RQLMetadata where
toJSON RQLMetadata{..} =
embedResourceVersion $ toJSON _rqlMetadata
where
embedResourceVersion (Object o) =
Object $ o <> "resource_version" .= _rqlMetadataResourceVersion
-- never happens since JSON value of RQL queries are always objects
embedResourceVersion _ = error "Unexpected: toJSON of RQLMetadata is not an object"
$(deriveJSON
defaultOptions { constructorTagModifier = snakeCase . drop 2
, sumEncoding = TaggedObject "type" "args"
}
''RQLMetadataV1)
$(deriveJSON
defaultOptions { constructorTagModifier = snakeCase . drop 4
, sumEncoding = TaggedObject "type" "args"
}
''RQLMetadataV2)
runMetadataQuery
:: ( HasVersion
, MonadIO m
, MonadBaseControl IO m
, Tracing.MonadTrace m
, MonadMetadataStorage m
, MonadResolveSource m
)
=> Env.Environment
-> InstanceId
-> UserInfo
-> HTTP.Manager
-> ServerConfigCtx
-> RebuildableSchemaCache
-> RQLMetadata
-> m (EncJSON, RebuildableSchemaCache)
runMetadataQuery env instanceId userInfo httpManager serverConfigCtx schemaCache RQLMetadata{..} = do
(metadata, currentResourceVersion) <- fetchMetadata
((r, modMetadata), modSchemaCache, cacheInvalidations) <-
runMetadataQueryM env currentResourceVersion _rqlMetadata
& runMetadataT metadata
& runCacheRWT schemaCache
& peelRun (RunCtx userInfo httpManager serverConfigCtx)
& runExceptT
& liftEitherM
-- set modified metadata in storage
if queryModifiesMetadata _rqlMetadata
then
case _sccMaintenanceMode serverConfigCtx of
MaintenanceModeDisabled -> do
-- set modified metadata in storage
newResourceVersion <- setMetadata (fromMaybe currentResourceVersion _rqlMetadataResourceVersion) modMetadata
-- notify schema cache sync
notifySchemaCacheSync newResourceVersion instanceId cacheInvalidations
(_, modSchemaCache', _) <- setMetadataResourceVersionInSchemaCache newResourceVersion
& runCacheRWT modSchemaCache
& peelRun (RunCtx userInfo httpManager serverConfigCtx)
& runExceptT
& liftEitherM
pure (r, modSchemaCache')
MaintenanceModeEnabled ->
throw500 "metadata cannot be modified in maintenance mode"
else
pure (r, modSchemaCache)
queryModifiesMetadata :: RQLMetadataRequest -> Bool
queryModifiesMetadata = \case
RMV1 q ->
case q of
RMPgRedeliverEvent _ -> False
RMPgInvokeEventTrigger _ -> False
RMGetInconsistentMetadata _ -> False
RMIntrospectRemoteSchema _ -> False
RMDumpInternalState _ -> False
RMSetCatalogState _ -> False
RMGetCatalogState _ -> False
RMExportMetadata _ -> False
RMGetEventInvocations _ -> False
RMGetScheduledEvents _ -> False
RMCreateScheduledEvent _ -> False
RMDeleteScheduledEvent _ -> False
RMBulk qs -> any queryModifiesMetadata qs
_ -> True
RMV2 q ->
case q of
RMV2ExportMetadata _ -> False
_ -> True
runMetadataQueryM
:: ( HasVersion
, MonadIO m
, MonadBaseControl IO m
, CacheRWM m
, Tracing.MonadTrace m
, UserInfoM m
, MonadUnique m
, HTTP.HasHttpManagerM m
, MetadataM m
, MonadMetadataStorageQueryAPI m
, HasServerConfigCtx m
)
=> Env.Environment
-> MetadataResourceVersion
-> RQLMetadataRequest
-> m EncJSON
runMetadataQueryM env currentResourceVersion = withPathK "args" . \case
RMV1 q -> runMetadataQueryV1M env currentResourceVersion q
RMV2 q -> runMetadataQueryV2M currentResourceVersion q
runMetadataQueryV1M
:: ( HasVersion
, MonadIO m
, MonadBaseControl IO m
, CacheRWM m
, Tracing.MonadTrace m
, UserInfoM m
, MonadUnique m
, HTTP.HasHttpManagerM m
, MetadataM m
, MonadMetadataStorageQueryAPI m
, HasServerConfigCtx m
)
=> Env.Environment
-> MetadataResourceVersion
-> RQLMetadataV1
-> m EncJSON
runMetadataQueryV1M env currentResourceVersion = \case
RMPgAddSource q -> runAddSource q
RMRenameSource q -> runRenameSource q
RMPgDropSource q -> runDropSource q
RMPgTrackTable q -> runTrackTableV2Q q
RMPgUntrackTable q -> runUntrackTableQ q
RMPgSetTableIsEnum q -> runSetExistingTableIsEnumQ q
RMPgSetTableCustomization q -> runSetTableCustomization q
RMPgTrackFunction q -> runTrackFunctionV2 q
RMPgUntrackFunction q -> runUntrackFunc q
RMPgCreateFunctionPermission q -> runCreateFunctionPermission q
RMPgDropFunctionPermission q -> runDropFunctionPermission q
RMPgCreateObjectRelationship q -> runCreateRelationship ObjRel q
RMPgCreateArrayRelationship q -> runCreateRelationship ArrRel q
RMPgDropRelationship q -> runDropRel q
RMPgSetRelationshipComment q -> runSetRelComment q
RMPgRenameRelationship q -> runRenameRel q
RMPgAddComputedField q -> runAddComputedField q
RMPgDropComputedField q -> runDropComputedField q
RMPgCreateRemoteRelationship q -> runCreateRemoteRelationship q
RMPgUpdateRemoteRelationship q -> runUpdateRemoteRelationship q
RMPgDeleteRemoteRelationship q -> runDeleteRemoteRelationship q
RMPgCreateInsertPermission q -> runCreatePerm q
RMPgCreateSelectPermission q -> runCreatePerm q
RMPgCreateUpdatePermission q -> runCreatePerm q
RMPgCreateDeletePermission q -> runCreatePerm q
RMPgDropInsertPermission q -> runDropPerm q
RMPgDropSelectPermission q -> runDropPerm q
RMPgDropUpdatePermission q -> runDropPerm q
RMPgDropDeletePermission q -> runDropPerm q
RMPgSetPermissionComment q -> runSetPermComment q
RMPgCreateEventTrigger q -> runCreateEventTriggerQuery q
RMPgDeleteEventTrigger q -> runDeleteEventTriggerQuery q
RMPgRedeliverEvent q -> runRedeliverEvent q
RMPgInvokeEventTrigger q -> runInvokeEventTrigger q
RMBigqueryAddSource q -> runAddSource q
RMBigqueryDropSource q -> runDropSource q
RMBigqueryTrackTable q -> runTrackTableV2Q q
RMBigqueryUntrackTable q -> runUntrackTableQ q
RMBigquerySetTableCustomization q -> runSetTableCustomization q
RMBigqueryCreateObjectRelationship q -> runCreateRelationship ObjRel q
RMBigqueryCreateArrayRelationship q -> runCreateRelationship ArrRel q
RMBigqueryDropRelationship q -> runDropRel q
RMBigquerySetRelationshipComment q -> runSetRelComment q
RMBigqueryRenameRelationship q -> runRenameRel q
RMBigqueryCreateInsertPermission q -> runCreatePerm q
RMBigqueryCreateSelectPermission q -> runCreatePerm q
RMBigqueryCreateUpdatePermission q -> runCreatePerm q
RMBigqueryCreateDeletePermission q -> runCreatePerm q
RMBigqueryDropInsertPermission q -> runDropPerm q
RMBigqueryDropSelectPermission q -> runDropPerm q
RMBigqueryDropUpdatePermission q -> runDropPerm q
RMBigqueryDropDeletePermission q -> runDropPerm q
RMBigquerySetPermissionComment q -> runSetPermComment q
RMMssqlAddSource q -> runAddSource q
RMMssqlDropSource q -> runDropSource q
RMMssqlTrackTable q -> runTrackTableV2Q q
RMMssqlUntrackTable q -> runUntrackTableQ q
RMMssqlSetTableCustomization q -> runSetTableCustomization q
RMMssqlCreateObjectRelationship q -> runCreateRelationship ObjRel q
RMMssqlCreateArrayRelationship q -> runCreateRelationship ArrRel q
RMMssqlDropRelationship q -> runDropRel q
RMMssqlSetRelationshipComment q -> runSetRelComment q
RMMssqlRenameRelationship q -> runRenameRel q
RMMssqlCreateInsertPermission q -> runCreatePerm q
RMMssqlCreateSelectPermission q -> runCreatePerm q
RMMssqlCreateUpdatePermission q -> runCreatePerm q
RMMssqlCreateDeletePermission q -> runCreatePerm q
RMMssqlDropInsertPermission q -> runDropPerm q
RMMssqlDropSelectPermission q -> runDropPerm q
RMMssqlDropUpdatePermission q -> runDropPerm q
RMMssqlDropDeletePermission q -> runDropPerm q
RMMssqlSetPermissionComment q -> runSetPermComment q
RMCitusAddSource q -> runAddSource q
RMCitusDropSource q -> runDropSource q
RMCitusTrackTable q -> runTrackTableV2Q q
RMCitusUntrackTable q -> runUntrackTableQ q
RMCitusSetTableCustomization q -> runSetTableCustomization q
RMCitusTrackFunction q -> runTrackFunctionV2 q
RMCitusUntrackFunction q -> runUntrackFunc q
RMCitusCreateFunctionPermission q -> runCreateFunctionPermission q
RMCitusDropFunctionPermission q -> runDropFunctionPermission q
RMCitusCreateObjectRelationship q -> runCreateRelationship ObjRel q
RMCitusCreateArrayRelationship q -> runCreateRelationship ArrRel q
RMCitusDropRelationship q -> runDropRel q
RMCitusSetRelationshipComment q -> runSetRelComment q
RMCitusRenameRelationship q -> runRenameRel q
RMCitusCreateInsertPermission q -> runCreatePerm q
RMCitusCreateSelectPermission q -> runCreatePerm q
RMCitusCreateUpdatePermission q -> runCreatePerm q
RMCitusCreateDeletePermission q -> runCreatePerm q
RMCitusDropInsertPermission q -> runDropPerm q
RMCitusDropSelectPermission q -> runDropPerm q
RMCitusDropUpdatePermission q -> runDropPerm q
RMCitusDropDeletePermission q -> runDropPerm q
RMCitusSetPermissionComment q -> runSetPermComment q
RMGetInconsistentMetadata q -> runGetInconsistentMetadata q
RMDropInconsistentMetadata q -> runDropInconsistentMetadata q
RMAddRemoteSchema q -> runAddRemoteSchema env q
RMUpdateRemoteSchema q -> runUpdateRemoteSchema env q
RMRemoveRemoteSchema q -> runRemoveRemoteSchema q
RMReloadRemoteSchema q -> runReloadRemoteSchema q
RMIntrospectRemoteSchema q -> runIntrospectRemoteSchema q
RMAddRemoteSchemaPermissions q -> runAddRemoteSchemaPermissions q
RMDropRemoteSchemaPermissions q -> runDropRemoteSchemaPermissions q
RMCreateCronTrigger q -> runCreateCronTrigger q
RMDeleteCronTrigger q -> runDeleteCronTrigger q
RMCreateScheduledEvent q -> runCreateScheduledEvent q
RMDeleteScheduledEvent q -> runDeleteScheduledEvent q
RMGetScheduledEvents q -> runGetScheduledEvents q
RMGetEventInvocations q -> runGetEventInvocations q
RMCreateQueryCollection q -> runCreateCollection q
RMDropQueryCollection q -> runDropCollection q
RMAddQueryToCollection q -> runAddQueryToCollection q
RMDropQueryFromCollection q -> runDropQueryFromCollection q
RMAddCollectionToAllowlist q -> runAddCollectionToAllowlist q
RMDropCollectionFromAllowlist q -> runDropCollectionFromAllowlist q
RMReplaceMetadata q -> runReplaceMetadata q
RMExportMetadata q -> runExportMetadata q
RMClearMetadata q -> runClearMetadata q
RMReloadMetadata q -> runReloadMetadata q
RMCreateAction q -> runCreateAction q
RMDropAction q -> runDropAction q
RMUpdateAction q -> runUpdateAction q
RMCreateActionPermission q -> runCreateActionPermission q
RMDropActionPermission q -> runDropActionPermission q
RMCreateRestEndpoint q -> runCreateEndpoint q
RMDropRestEndpoint q -> runDropEndpoint q
RMSetCustomTypes q -> runSetCustomTypes q
RMDumpInternalState q -> runDumpInternalState q
RMGetCatalogState q -> runGetCatalogState q
RMSetCatalogState q -> runSetCatalogState q
RMSetApiLimits q -> runSetApiLimits q
RMRemoveApiLimits -> runRemoveApiLimits
RMSetMetricsConfig q -> runSetMetricsConfig q
RMRemoveMetricsConfig -> runRemoveMetricsConfig
[Preview] Inherited roles for postgres read queries fixes #3868 docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de` Note: To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`. Introduction ------------ This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`. How are select permissions of different roles are combined? ------------------------------------------------------------ A select permission includes 5 things: 1. Columns accessible to the role 2. Row selection filter 3. Limit 4. Allow aggregation 5. Scalar computed fields accessible to the role Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`. Let's say the following GraphQL query is queried with the `combined_roles` role. ```graphql query { employees { address phone } } ``` This will translate to the following SQL query: ```sql select (case when (P1 or P2) then address else null end) as address, (case when P2 then phone else null end) as phone from employee where (P1 or P2) ``` The other parameters of the select permission will be combined in the following manner: 1. Limit - Minimum of the limits will be the limit of the inherited role 2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation 3. Scalar computed fields - same as table column fields, as in the above example APIs for inherited roles: ---------------------- 1. `add_inherited_role` `add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments `role_name`: the name of the inherited role to be added (String) `role_set`: list of roles that need to be combined (Array of Strings) Example: ```json { "type": "add_inherited_role", "args": { "role_name":"combined_user", "role_set":[ "user", "user1" ] } } ``` After adding the inherited role, the inherited role can be used like single roles like earlier Note: An inherited role can only be created with non-inherited/singular roles. 2. `drop_inherited_role` The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument: `role_name`: name of the inherited role to be dropped Example: ```json { "type": "drop_inherited_role", "args": { "role_name":"combined_user" } } ``` Metadata --------- The derived roles metadata will be included under the `experimental_features` key while exporting the metadata. ```json { "experimental_features": { "derived_roles": [ { "role_name": "manager_is_employee_too", "role_set": [ "employee", "manager" ] } ] } } ``` Scope ------ Only postgres queries and subscriptions are supported in this PR. Important points: ----------------- 1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done. TODOs ------- - [ ] Tests - [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features - [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?) - [ ] Introspection test with a inherited role (nullability changes in a inherited role) - [ ] Docs - [ ] Changelog Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com> GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
RMAddInheritedRole q -> runAddInheritedRole q
RMDropInheritedRole q -> runDropInheritedRole q
RMSetGraphqlSchemaIntrospectionOptions q -> runSetGraphqlSchemaIntrospectionOptions q
RMBulk q -> encJFromList <$> indexedMapM (runMetadataQueryM env currentResourceVersion) q
runMetadataQueryV2M
:: ( MonadIO m
, CacheRWM m
, MetadataM m
, MonadMetadataStorageQueryAPI m
[Preview] Inherited roles for postgres read queries fixes #3868 docker image - `hasura/graphql-engine:inherited-roles-preview-48b73a2de` Note: To be able to use the inherited roles feature, the graphql-engine should be started with the env variable `HASURA_GRAPHQL_EXPERIMENTAL_FEATURES` set to `inherited_roles`. Introduction ------------ This PR implements the idea of multiple roles as presented in this [paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/FGALanguageICDE07.pdf). The multiple roles feature in this PR can be used via inherited roles. An inherited role is a role which can be created by combining multiple singular roles. For example, if there are two roles `author` and `editor` configured in the graphql-engine, then we can create a inherited role with the name of `combined_author_editor` role which will combine the select permissions of the `author` and `editor` roles and then make GraphQL queries using the `combined_author_editor`. How are select permissions of different roles are combined? ------------------------------------------------------------ A select permission includes 5 things: 1. Columns accessible to the role 2. Row selection filter 3. Limit 4. Allow aggregation 5. Scalar computed fields accessible to the role Suppose there are two roles, `role1` gives access to the `address` column with row filter `P1` and `role2` gives access to both the `address` and the `phone` column with row filter `P2` and we create a new role `combined_roles` which combines `role1` and `role2`. Let's say the following GraphQL query is queried with the `combined_roles` role. ```graphql query { employees { address phone } } ``` This will translate to the following SQL query: ```sql select (case when (P1 or P2) then address else null end) as address, (case when P2 then phone else null end) as phone from employee where (P1 or P2) ``` The other parameters of the select permission will be combined in the following manner: 1. Limit - Minimum of the limits will be the limit of the inherited role 2. Allow aggregations - If any of the role allows aggregation, then the inherited role will allow aggregation 3. Scalar computed fields - same as table column fields, as in the above example APIs for inherited roles: ---------------------- 1. `add_inherited_role` `add_inherited_role` is the [metadata API](https://hasura.io/docs/1.0/graphql/core/api-reference/index.html#schema-metadata-api) to create a new inherited role. It accepts two arguments `role_name`: the name of the inherited role to be added (String) `role_set`: list of roles that need to be combined (Array of Strings) Example: ```json { "type": "add_inherited_role", "args": { "role_name":"combined_user", "role_set":[ "user", "user1" ] } } ``` After adding the inherited role, the inherited role can be used like single roles like earlier Note: An inherited role can only be created with non-inherited/singular roles. 2. `drop_inherited_role` The `drop_inherited_role` API accepts the name of the inherited role and drops it from the metadata. It accepts a single argument: `role_name`: name of the inherited role to be dropped Example: ```json { "type": "drop_inherited_role", "args": { "role_name":"combined_user" } } ``` Metadata --------- The derived roles metadata will be included under the `experimental_features` key while exporting the metadata. ```json { "experimental_features": { "derived_roles": [ { "role_name": "manager_is_employee_too", "role_set": [ "employee", "manager" ] } ] } } ``` Scope ------ Only postgres queries and subscriptions are supported in this PR. Important points: ----------------- 1. All columns exposed to an inherited role will be marked as `nullable`, this is done so that cell value nullification can be done. TODOs ------- - [ ] Tests - [ ] Test a GraphQL query running with a inherited role without enabling inherited roles in experimental features - [] Tests for aggregate queries, limit, computed fields, functions, subscriptions (?) - [ ] Introspection test with a inherited role (nullability changes in a inherited role) - [ ] Docs - [ ] Changelog Co-authored-by: Vamshi Surabhi <6562944+0x777@users.noreply.github.com> GitOrigin-RevId: 3b8ee1e11f5ceca80fe294f8c074d42fbccfec63
2021-03-08 14:14:13 +03:00
, HasServerConfigCtx m
)
=> MetadataResourceVersion
-> RQLMetadataV2
-> m EncJSON
runMetadataQueryV2M currentResourceVersion = \case
RMV2ReplaceMetadata q -> runReplaceMetadataV2 q
RMV2ExportMetadata q -> runExportMetadataV2 currentResourceVersion q