mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-13 19:33:55 +03:00
Support nested object fields in DC API and use this to implement nest…
## Description This change adds support for nested object fields in HGE IR and Schema Cache, the Data Connectors backend and API, and the MongoDB agent. ### Data Connector API changes - The `/schema` endpoint response now includes an optional set of GraphQL type definitions. Table column types can refer to these definitions by name. - Queries can now include a new field type `object` which contains a column name and a nested query. This allows querying into a nested object within a field. ### MongoDB agent changes - Add support for querying into nested documents using the new `object` field type. ### HGE changes - The `Backend` type class has a new type family `XNestedObjects b` which controls whether or not a backend supports querying into nested objects. This is currently enabled only for the `DataConnector` backend. - For backends that support nested objects, the `FieldInfo` type gets a new constructor `FINestedObject`, and the `AnnFieldG` type gets a new constructor `AFNestedObject`. - If the DC `/schema` endpoint returns any custom GraphQL type definitions they are stored in the `TableInfo` for each table in the source. - During schema cache building, the function `addNonColumnFields` will check whether any column types match custom GraphQL object types stored in the `TableInfo`. If so, they are converted into `FINestedObject` instead of `FIColumn` in the `FieldInfoMap`. - When building the `FieldParser`s from `FieldInfo` (function `fieldSelection`) any `FINestedObject` fields are converted into nested object parsers returning `AFNestedObject`. - The `DataConnector` query planner converts `AFNestedObject` fields into `object` field types in the query sent to the agent. ## Limitations ### HGE not yet implemented: - Support for nested arrays - Support for nested objects/arrays in mutations - Support for nested objects/arrays in order-by - Support for filters (`where`) in nested objects/arrays - Support for adding custom GraphQL types via track table metadata API - Support for interface and union types - Tests for nested objects ### Mongo agent not yet implemented: - Generate nested object types from validation schema - Support for aggregates - Support for order-by - Configure agent port - Build agent in CI - Agent tests for nested objects and MongoDB agent PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7844 GitOrigin-RevId: aec9ec1e4216293286a68f9b1af6f3f5317db423
This commit is contained in:
parent
a1cd2b8176
commit
346804fc67
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hasura/dc-api-types",
|
||||
"version": "0.29.0",
|
||||
"version": "0.30.0",
|
||||
"description": "Hasura GraphQL Engine Data Connector Agent API types",
|
||||
"author": "Hasura (https://github.com/hasura/graphql-engine)",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -1035,6 +1035,13 @@
|
||||
},
|
||||
"SchemaResponse": {
|
||||
"properties": {
|
||||
"objectTypes": {
|
||||
"description": "Object type definitions referenced in this schema",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ObjectTypeDefinition"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"tables": {
|
||||
"description": "Available tables",
|
||||
"items": {
|
||||
@ -1241,6 +1248,30 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ObjectTypeDefinition": {
|
||||
"properties": {
|
||||
"columns": {
|
||||
"description": "The columns of the type",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ColumnInfo"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"description": {
|
||||
"description": "The description of the type",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the type",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"columns"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"QueryResponse": {
|
||||
"properties": {
|
||||
"aggregates": {
|
||||
@ -1413,6 +1444,28 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NestedObjectField": {
|
||||
"properties": {
|
||||
"column": {
|
||||
"type": "string"
|
||||
},
|
||||
"query": {
|
||||
"$ref": "#/components/schemas/Query"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"object"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"column",
|
||||
"query",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RelationshipField": {
|
||||
"properties": {
|
||||
"query": {
|
||||
@ -1462,11 +1515,15 @@
|
||||
"discriminator": {
|
||||
"mapping": {
|
||||
"column": "ColumnField",
|
||||
"object": "NestedObjectField",
|
||||
"relationship": "RelationshipField"
|
||||
},
|
||||
"propertyName": "type"
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NestedObjectField"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RelationshipField"
|
||||
},
|
||||
|
@ -63,12 +63,14 @@ export type { MutationOperation } from './models/MutationOperation';
|
||||
export type { MutationOperationResults } from './models/MutationOperationResults';
|
||||
export type { MutationRequest } from './models/MutationRequest';
|
||||
export type { MutationResponse } from './models/MutationResponse';
|
||||
export type { NestedObjectField } from './models/NestedObjectField';
|
||||
export type { NotExpression } from './models/NotExpression';
|
||||
export type { NullColumnFieldValue } from './models/NullColumnFieldValue';
|
||||
export type { NullColumnInsertFieldValue } from './models/NullColumnInsertFieldValue';
|
||||
export type { ObjectRelationInsertFieldValue } from './models/ObjectRelationInsertFieldValue';
|
||||
export type { ObjectRelationInsertionOrder } from './models/ObjectRelationInsertionOrder';
|
||||
export type { ObjectRelationInsertSchema } from './models/ObjectRelationInsertSchema';
|
||||
export type { ObjectTypeDefinition } from './models/ObjectTypeDefinition';
|
||||
export type { OpenApiDiscriminator } from './models/OpenApiDiscriminator';
|
||||
export type { OpenApiExternalDocumentation } from './models/OpenApiExternalDocumentation';
|
||||
export type { OpenApiReference } from './models/OpenApiReference';
|
||||
|
@ -3,7 +3,8 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import type { ColumnField } from './ColumnField';
|
||||
import type { NestedObjectField } from './NestedObjectField';
|
||||
import type { RelationshipField } from './RelationshipField';
|
||||
|
||||
export type Field = (RelationshipField | ColumnField);
|
||||
export type Field = (NestedObjectField | RelationshipField | ColumnField);
|
||||
|
||||
|
12
dc-agents/dc-api-types/src/models/NestedObjectField.ts
Normal file
12
dc-agents/dc-api-types/src/models/NestedObjectField.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import type { Query } from './Query';
|
||||
|
||||
export type NestedObjectField = {
|
||||
column: string;
|
||||
query: Query;
|
||||
type: 'object';
|
||||
};
|
||||
|
21
dc-agents/dc-api-types/src/models/ObjectTypeDefinition.ts
Normal file
21
dc-agents/dc-api-types/src/models/ObjectTypeDefinition.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import type { ColumnInfo } from './ColumnInfo';
|
||||
|
||||
export type ObjectTypeDefinition = {
|
||||
/**
|
||||
* The columns of the type
|
||||
*/
|
||||
columns: Array<ColumnInfo>;
|
||||
/**
|
||||
* The description of the type
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* The name of the type
|
||||
*/
|
||||
name: string;
|
||||
};
|
||||
|
@ -2,9 +2,14 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import type { ObjectTypeDefinition } from './ObjectTypeDefinition';
|
||||
import type { TableInfo } from './TableInfo';
|
||||
|
||||
export type SchemaResponse = {
|
||||
/**
|
||||
* Object type definitions referenced in this schema
|
||||
*/
|
||||
objectTypes?: Array<ObjectTypeDefinition>;
|
||||
/**
|
||||
* Available tables
|
||||
*/
|
||||
|
10
dc-agents/package-lock.json
generated
10
dc-agents/package-lock.json
generated
@ -24,7 +24,7 @@
|
||||
},
|
||||
"dc-api-types": {
|
||||
"name": "@hasura/dc-api-types",
|
||||
"version": "0.29.0",
|
||||
"version": "0.30.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@tsconfig/node16": "^1.0.3",
|
||||
@ -2227,7 +2227,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.1.0",
|
||||
"@hasura/dc-api-types": "0.29.0",
|
||||
"@hasura/dc-api-types": "0.30.0",
|
||||
"fastify": "^4.13.0",
|
||||
"mathjs": "^11.0.0",
|
||||
"pino-pretty": "^8.0.0",
|
||||
@ -2547,7 +2547,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.1.0",
|
||||
"@hasura/dc-api-types": "0.29.0",
|
||||
"@hasura/dc-api-types": "0.30.0",
|
||||
"fastify": "^4.13.0",
|
||||
"fastify-metrics": "^9.2.1",
|
||||
"nanoid": "^3.3.4",
|
||||
@ -2868,7 +2868,7 @@
|
||||
"version": "file:reference",
|
||||
"requires": {
|
||||
"@fastify/cors": "^8.1.0",
|
||||
"@hasura/dc-api-types": "0.29.0",
|
||||
"@hasura/dc-api-types": "0.30.0",
|
||||
"@tsconfig/node16": "^1.0.3",
|
||||
"@types/node": "^16.11.49",
|
||||
"@types/xml2js": "^0.4.11",
|
||||
@ -3080,7 +3080,7 @@
|
||||
"version": "file:sqlite",
|
||||
"requires": {
|
||||
"@fastify/cors": "^8.1.0",
|
||||
"@hasura/dc-api-types": "0.29.0",
|
||||
"@hasura/dc-api-types": "0.30.0",
|
||||
"@tsconfig/node16": "^1.0.3",
|
||||
"@types/node": "^16.11.49",
|
||||
"@types/sqlite3": "^3.1.8",
|
||||
|
4
dc-agents/reference/package-lock.json
generated
4
dc-agents/reference/package-lock.json
generated
@ -10,7 +10,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.1.0",
|
||||
"@hasura/dc-api-types": "0.29.0",
|
||||
"@hasura/dc-api-types": "0.30.0",
|
||||
"fastify": "^4.13.0",
|
||||
"mathjs": "^11.0.0",
|
||||
"pino-pretty": "^8.0.0",
|
||||
@ -52,7 +52,7 @@
|
||||
"integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
|
||||
},
|
||||
"node_modules/@hasura/dc-api-types": {
|
||||
"version": "0.29.0",
|
||||
"version": "0.30.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@tsconfig/node16": "^1.0.3",
|
||||
|
@ -22,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.1.0",
|
||||
"@hasura/dc-api-types": "0.29.0",
|
||||
"@hasura/dc-api-types": "0.30.0",
|
||||
"fastify": "^4.13.0",
|
||||
"mathjs": "^11.0.0",
|
||||
"pino-pretty": "^8.0.0",
|
||||
|
@ -452,6 +452,9 @@ const projectRow = (fields: Record<string, Field>, findRelationship: (relationsh
|
||||
projectedRow[fieldName] = subquery ? performQuery(relationship.target_table, subquery) : { aggregates: null, rows: null };
|
||||
break;
|
||||
|
||||
case "object":
|
||||
throw new Error('Unsupported field type "object"');
|
||||
|
||||
default:
|
||||
return unreachable(field["type"]);
|
||||
}
|
||||
|
4
dc-agents/sqlite/package-lock.json
generated
4
dc-agents/sqlite/package-lock.json
generated
@ -10,7 +10,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.1.0",
|
||||
"@hasura/dc-api-types": "0.29.0",
|
||||
"@hasura/dc-api-types": "0.30.0",
|
||||
"fastify": "^4.13.0",
|
||||
"fastify-metrics": "^9.2.1",
|
||||
"nanoid": "^3.3.4",
|
||||
@ -57,7 +57,7 @@
|
||||
"integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
|
||||
},
|
||||
"node_modules/@hasura/dc-api-types": {
|
||||
"version": "0.29.0",
|
||||
"version": "0.30.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@tsconfig/node16": "^1.0.3",
|
||||
|
@ -22,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.1.0",
|
||||
"@hasura/dc-api-types": "0.29.0",
|
||||
"@hasura/dc-api-types": "0.30.0",
|
||||
"fastify-metrics": "^9.2.1",
|
||||
"fastify": "^4.13.0",
|
||||
"nanoid": "^3.3.4",
|
||||
|
@ -107,6 +107,8 @@ export function json_object(relationships: TableRelationships[], fields: Fields,
|
||||
throw new Error(`Couldn't find relationship ${field.relationship} for field ${fieldName} on table ${table}`);
|
||||
}
|
||||
return `'${fieldName}', ${relationship(relationships, rel, field, tableAlias)}`;
|
||||
case "object":
|
||||
throw new Error('Unsupported field type "object"');
|
||||
default:
|
||||
return unreachable(field["type"]);
|
||||
}
|
||||
|
@ -24,10 +24,15 @@ module Hasura.Backends.DataConnector.API.V0.Query
|
||||
FieldValue,
|
||||
mkColumnFieldValue,
|
||||
mkRelationshipFieldValue,
|
||||
mkNestedObjFieldValue,
|
||||
mkNestedArrayFieldValue,
|
||||
deserializeAsColumnFieldValue,
|
||||
deserializeAsRelationshipFieldValue,
|
||||
deserializeAsNestedObjFieldValue,
|
||||
deserializeAsNestedArrayFieldValue,
|
||||
_ColumnFieldValue,
|
||||
_RelationshipFieldValue,
|
||||
_NestedObjFieldValue,
|
||||
)
|
||||
where
|
||||
|
||||
@ -158,6 +163,7 @@ relationshipFieldObjectCodec =
|
||||
data Field
|
||||
= ColumnField API.V0.ColumnName API.V0.ScalarType
|
||||
| RelField RelationshipField
|
||||
| NestedObjField API.V0.ColumnName Query
|
||||
deriving stock (Eq, Ord, Show, Generic)
|
||||
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec Field
|
||||
|
||||
@ -169,15 +175,25 @@ instance HasCodec Field where
|
||||
where
|
||||
columnCodec =
|
||||
(,)
|
||||
<$> requiredField' "column" .= fst
|
||||
<*> requiredField' "column_type" .= snd
|
||||
<$> requiredField' "column"
|
||||
.= fst
|
||||
<*> requiredField' "column_type"
|
||||
.= snd
|
||||
nestedObjCodec =
|
||||
(,)
|
||||
<$> requiredField' "column"
|
||||
.= fst
|
||||
<*> requiredField' "query"
|
||||
.= snd
|
||||
enc = \case
|
||||
ColumnField columnName scalarType -> ("column", mapToEncoder (columnName, scalarType) columnCodec)
|
||||
RelField relField -> ("relationship", mapToEncoder relField relationshipFieldObjectCodec)
|
||||
NestedObjField columnName nestedObjQuery -> ("object", mapToEncoder (columnName, nestedObjQuery) nestedObjCodec)
|
||||
dec =
|
||||
HashMap.fromList
|
||||
[ ("column", ("ColumnField", mapToDecoder (uncurry ColumnField) columnCodec)),
|
||||
("relationship", ("RelationshipField", mapToDecoder RelField relationshipFieldObjectCodec))
|
||||
("relationship", ("RelationshipField", mapToDecoder RelField relationshipFieldObjectCodec)),
|
||||
("object", ("NestedObjectField", mapToDecoder (uncurry NestedObjField) nestedObjCodec))
|
||||
]
|
||||
|
||||
-- | The resolved query response provided by the 'POST /query'
|
||||
@ -235,7 +251,8 @@ instance Ord FieldValue where
|
||||
instance Show FieldValue where
|
||||
showsPrec d fieldValue =
|
||||
case deserializeFieldValueByGuessing fieldValue of
|
||||
Left columnFieldValue -> showParen (d > appPrec) $ showString "ColumnFieldValue " . showsPrec appPrec1 columnFieldValue
|
||||
Left (Left columnFieldValue) -> showParen (d > appPrec) $ showString "ColumnFieldValue " . showsPrec appPrec1 columnFieldValue
|
||||
Left (Right nestedObjFieldValue) -> showParen (d > appPrec) $ showString "NestedObjFieldValue " . showsPrec appPrec1 nestedObjFieldValue
|
||||
Right queryResponse -> showParen (d > appPrec) $ showString "RelationshipFieldValue " . showsPrec appPrec1 queryResponse
|
||||
|
||||
mkColumnFieldValue :: J.Value -> FieldValue
|
||||
@ -244,6 +261,12 @@ mkColumnFieldValue = FieldValue
|
||||
mkRelationshipFieldValue :: QueryResponse -> FieldValue
|
||||
mkRelationshipFieldValue = FieldValue . J.toJSON
|
||||
|
||||
mkNestedObjFieldValue :: HashMap FieldName FieldValue -> FieldValue
|
||||
mkNestedObjFieldValue = FieldValue . J.toJSON
|
||||
|
||||
mkNestedArrayFieldValue :: [FieldValue] -> FieldValue
|
||||
mkNestedArrayFieldValue = FieldValue . J.toJSON
|
||||
|
||||
deserializeAsColumnFieldValue :: FieldValue -> J.Value
|
||||
deserializeAsColumnFieldValue (FieldValue value) = value
|
||||
|
||||
@ -253,9 +276,26 @@ deserializeAsRelationshipFieldValue (FieldValue value) =
|
||||
J.Error s -> Left $ T.pack s
|
||||
J.Success queryResponse -> Right queryResponse
|
||||
|
||||
deserializeFieldValueByGuessing :: FieldValue -> Either J.Value QueryResponse
|
||||
deserializeAsNestedObjFieldValue :: FieldValue -> Either Text (HashMap FieldName FieldValue)
|
||||
deserializeAsNestedObjFieldValue (FieldValue value) =
|
||||
case J.fromJSON value of
|
||||
J.Error s -> Left $ T.pack s
|
||||
J.Success obj -> Right obj
|
||||
|
||||
deserializeAsNestedArrayFieldValue :: FieldValue -> Either Text [FieldValue]
|
||||
deserializeAsNestedArrayFieldValue (FieldValue value) =
|
||||
case J.fromJSON value of
|
||||
J.Error s -> Left $ T.pack s
|
||||
J.Success obj -> Right obj
|
||||
|
||||
deserializeFieldValueByGuessing :: FieldValue -> (Either (Either (Either Value (HashMap FieldName FieldValue)) [FieldValue]) QueryResponse)
|
||||
deserializeFieldValueByGuessing fieldValue =
|
||||
left (const $ deserializeAsColumnFieldValue fieldValue) $ deserializeAsRelationshipFieldValue fieldValue
|
||||
left
|
||||
( const $
|
||||
left (const $ left (const $ deserializeAsColumnFieldValue fieldValue) $ deserializeAsNestedObjFieldValue fieldValue) $
|
||||
deserializeAsNestedArrayFieldValue fieldValue
|
||||
)
|
||||
$ deserializeAsRelationshipFieldValue fieldValue
|
||||
|
||||
-- | Even though we could just describe a FieldValue as "any JSON value", we're explicitly
|
||||
-- describing it in terms of either a 'QueryResponse' or "any JSON value", in order to
|
||||
@ -288,6 +328,12 @@ _ColumnFieldValue = lens deserializeAsColumnFieldValue (const mkColumnFieldValue
|
||||
_RelationshipFieldValue :: Prism' FieldValue QueryResponse
|
||||
_RelationshipFieldValue = prism' mkRelationshipFieldValue (either (const Nothing) Just . deserializeAsRelationshipFieldValue)
|
||||
|
||||
_NestedObjFieldValue :: Prism' FieldValue (HashMap FieldName FieldValue)
|
||||
_NestedObjFieldValue = prism' mkNestedObjFieldValue (either (const Nothing) Just . deserializeAsNestedObjFieldValue)
|
||||
|
||||
_NestedArrayFieldValue :: Prism' FieldValue [FieldValue]
|
||||
_NestedArrayFieldValue = prism' mkNestedArrayFieldValue (either (const Nothing) Just . deserializeAsNestedArrayFieldValue)
|
||||
|
||||
$(makeLenses ''QueryRequest)
|
||||
$(makeLenses ''Query)
|
||||
$(makeLenses ''QueryResponse)
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
module Hasura.Backends.DataConnector.API.V0.Schema
|
||||
( SchemaResponse (..),
|
||||
ObjectTypeDefinition (..),
|
||||
)
|
||||
where
|
||||
|
||||
@ -9,9 +10,13 @@ import Autodocodec
|
||||
import Control.DeepSeq (NFData)
|
||||
import Data.Aeson (FromJSON, ToJSON)
|
||||
import Data.Hashable (Hashable)
|
||||
import Data.List.NonEmpty (NonEmpty)
|
||||
import Data.OpenApi (ToSchema)
|
||||
import Data.Text (Text)
|
||||
import GHC.Generics (Generic)
|
||||
import Hasura.Backends.DataConnector.API.V0.Column qualified as API.V0
|
||||
import Hasura.Backends.DataConnector.API.V0.Table qualified as API.V0
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
import Servant.API qualified as Servant
|
||||
import Prelude
|
||||
|
||||
@ -20,17 +25,37 @@ import Prelude
|
||||
|
||||
-- | The Schema Response provides the schemas for tracked tables and
|
||||
-- 'Capabilities' supported by the service.
|
||||
newtype SchemaResponse = SchemaResponse
|
||||
{ _srTables :: [API.V0.TableInfo]
|
||||
data SchemaResponse = SchemaResponse
|
||||
{ _srTables :: [API.V0.TableInfo],
|
||||
_srObjectTypes :: Maybe (NonEmpty ObjectTypeDefinition)
|
||||
}
|
||||
deriving stock (Eq, Ord, Show, Generic)
|
||||
deriving stock (Eq, Show, Generic)
|
||||
deriving anyclass (NFData, Hashable)
|
||||
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec SchemaResponse
|
||||
|
||||
instance HasCodec SchemaResponse where
|
||||
codec =
|
||||
object "SchemaResponse" $
|
||||
SchemaResponse <$> requiredField "tables" "Available tables" .= _srTables
|
||||
SchemaResponse
|
||||
<$> requiredField "tables" "Available tables" .= _srTables
|
||||
<*> optionalField "objectTypes" "Object type definitions referenced in this schema" .= _srObjectTypes
|
||||
|
||||
instance Servant.HasStatus SchemaResponse where
|
||||
type StatusOf SchemaResponse = 200
|
||||
|
||||
data ObjectTypeDefinition = ObjectTypeDefinition
|
||||
{ _otdName :: G.Name,
|
||||
_otdDescription :: Maybe Text,
|
||||
_otdColumns :: NonEmpty API.V0.ColumnInfo
|
||||
}
|
||||
deriving stock (Eq, Show, Generic)
|
||||
deriving anyclass (NFData, Hashable)
|
||||
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec ObjectTypeDefinition
|
||||
|
||||
instance HasCodec ObjectTypeDefinition where
|
||||
codec =
|
||||
object "ObjectTypeDefinition" $
|
||||
ObjectTypeDefinition
|
||||
<$> requiredField "name" "The name of the type" .= _otdName
|
||||
<*> optionalField "description" "The description of the type" .= _otdDescription
|
||||
<*> requiredField "columns" "The columns of the type" .= _otdColumns
|
||||
|
@ -77,7 +77,16 @@ schemaTables :: [API.TableInfo]
|
||||
schemaTables = either error id . eitherDecodeStrict $ schemaBS
|
||||
|
||||
numericColumns :: [API.ColumnName]
|
||||
numericColumns = schemaTables >>= (API._tiColumns >>> mapMaybe (\API.ColumnInfo {..} -> if _ciType == API.ScalarType "number" then Just _ciName else Nothing))
|
||||
numericColumns =
|
||||
schemaTables
|
||||
>>= ( API._tiColumns
|
||||
>>> mapMaybe
|
||||
( \API.ColumnInfo {..} ->
|
||||
if _ciType == API.ScalarType "number"
|
||||
then Just _ciName
|
||||
else Nothing
|
||||
)
|
||||
)
|
||||
|
||||
edgeCasesSchemaBS :: ByteString
|
||||
edgeCasesSchemaBS = $(makeRelativeToProject "test/Test/Data/edge-cases-schema-tables.json" >>= embedFile)
|
||||
|
@ -826,7 +826,8 @@ schema =
|
||||
API._tiUpdatable = True,
|
||||
API._tiDeletable = True
|
||||
}
|
||||
]
|
||||
],
|
||||
_srObjectTypes = Nothing
|
||||
}
|
||||
|
||||
-- | Stock 'MockConfig' for a Chinook Agent.
|
||||
|
@ -132,7 +132,8 @@ resolveSource sourceConfig =
|
||||
_ptmiForeignKeys = mempty,
|
||||
_ptmiViewInfo = Just $ ViewInfo False False False,
|
||||
_ptmiDescription = Nothing,
|
||||
_ptmiExtraTableMetadata = ()
|
||||
_ptmiExtraTableMetadata = (),
|
||||
_ptmiCustomObjectTypes = mempty
|
||||
}
|
||||
)
|
||||
| (index, RestTable {tableReference, schema}) <-
|
||||
|
@ -77,6 +77,7 @@ instance Backend 'DataConnector where
|
||||
type XEventTriggers 'DataConnector = XDisable
|
||||
type XNestedInserts 'DataConnector = XDisable
|
||||
type XStreamingSubscription 'DataConnector = XDisable
|
||||
type XNestedObjects 'DataConnector = XEnable
|
||||
|
||||
type HealthCheckTest 'DataConnector = Void
|
||||
|
||||
|
@ -35,6 +35,7 @@ import Hasura.RQL.IR.BoolExp (OpExpG (..), PartialSQLExp (..), RootOrCurrent (..
|
||||
import Hasura.RQL.Types.Backend (Backend)
|
||||
import Hasura.RQL.Types.Column qualified as RQL.T.C
|
||||
import Hasura.RQL.Types.Common (OID (..), SourceName)
|
||||
import Hasura.RQL.Types.CustomTypes (GraphQLType (..))
|
||||
import Hasura.RQL.Types.EventTrigger (RecreateEventTriggers (RETDoNothing))
|
||||
import Hasura.RQL.Types.Metadata (BackendConfigWrapper, SourceMetadata (..))
|
||||
import Hasura.RQL.Types.Metadata qualified as Metadata
|
||||
@ -55,6 +56,7 @@ import Hasura.Services.Network
|
||||
import Hasura.Session (SessionVariable, mkSessionVariable)
|
||||
import Hasura.Tracing (ignoreTraceT)
|
||||
import Hasura.Tracing qualified as Tracing
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
import Language.GraphQL.Draft.Syntax qualified as GQL
|
||||
import Network.HTTP.Client qualified as HTTP
|
||||
import Servant.API (Union)
|
||||
@ -81,6 +83,7 @@ instance BackendMetadata 'DataConnector where
|
||||
postDropSourceHook _sourceConfig _tableTriggerMap = pure ()
|
||||
buildComputedFieldBooleanExp _ _ _ _ _ _ =
|
||||
error "buildComputedFieldBooleanExp: not implemented for the Data Connector backend."
|
||||
columnInfoToFieldInfo = columnInfoToFieldInfo'
|
||||
listAllTables = listAllTables'
|
||||
supportsBeingRemoteRelationshipTarget = supportsBeingRemoteRelationshipTarget'
|
||||
|
||||
@ -192,7 +195,10 @@ resolveDatabaseMetadata' ::
|
||||
DC.SourceConfig ->
|
||||
m (Either QErr (DBObjectsIntrospection 'DataConnector))
|
||||
resolveDatabaseMetadata' _ DC.SourceConfig {_scSchema = API.SchemaResponse {..}, ..} =
|
||||
let tables = HashMap.fromList $ do
|
||||
let typeNames = maybe mempty (HashSet.fromList . toList . fmap API._otdName) _srObjectTypes
|
||||
customObjectTypes =
|
||||
maybe mempty (HashMap.fromList . mapMaybe (toTableObjectType _scCapabilities typeNames) . toList) _srObjectTypes
|
||||
tables = HashMap.fromList $ do
|
||||
API.TableInfo {..} <- _srTables
|
||||
let primaryKeyColumns = fmap Witch.from . NESeq.fromList <$> _tiPrimaryKey
|
||||
let meta =
|
||||
@ -224,7 +230,8 @@ resolveDatabaseMetadata' _ DC.SourceConfig {_scSchema = API.SchemaResponse {..},
|
||||
_tiColumns
|
||||
& fmap (\API.ColumnInfo {..} -> (Witch.from _ciName, DC.ExtraColumnMetadata _ciValueGenerated))
|
||||
& HashMap.fromList
|
||||
}
|
||||
},
|
||||
_ptmiCustomObjectTypes = Just customObjectTypes
|
||||
}
|
||||
pure (coerce _tiName, meta)
|
||||
in pure $
|
||||
@ -235,6 +242,25 @@ resolveDatabaseMetadata' _ DC.SourceConfig {_scSchema = API.SchemaResponse {..},
|
||||
_rsScalars = mempty
|
||||
}
|
||||
|
||||
toTableObjectType :: API.Capabilities -> HashSet G.Name -> API.ObjectTypeDefinition -> Maybe (G.Name, RQL.T.T.TableObjectType 'DataConnector)
|
||||
toTableObjectType capabilities typeNames API.ObjectTypeDefinition {..} =
|
||||
(_otdName,) . RQL.T.T.TableObjectType _otdName (G.Description <$> _otdDescription) <$> traverse toTableObjectFieldDefinition _otdColumns
|
||||
where
|
||||
toTableObjectFieldDefinition API.ColumnInfo {..} = do
|
||||
fieldTypeName <- G.mkName $ API.getScalarType _ciType
|
||||
fieldName <- G.mkName $ API.unColumnName _ciName
|
||||
pure $
|
||||
RQL.T.T.TableObjectFieldDefinition
|
||||
{ _tofdColumn = Witch.from _ciName,
|
||||
_tofdName = fieldName,
|
||||
_tofdDescription = G.Description <$> _ciDescription,
|
||||
_tofdGType = GraphQLType $ G.TypeNamed (G.Nullability _ciNullable) fieldTypeName,
|
||||
_tofdFieldType =
|
||||
if HashSet.member fieldTypeName typeNames
|
||||
then RQL.T.T.TOFTObject fieldTypeName
|
||||
else RQL.T.T.TOFTScalar fieldTypeName $ DC.mkScalarType capabilities _ciType
|
||||
}
|
||||
|
||||
-- | Construct a 'HashSet' 'RQL.T.T.ForeignKeyMetadata'
|
||||
-- 'DataConnector' to build the foreign key constraints in the table
|
||||
-- metadata.
|
||||
@ -390,6 +416,30 @@ mkTypedSessionVar columnType =
|
||||
errorAction :: MonadError QErr m => API.ErrorResponse -> m a
|
||||
errorAction e = throw400WithDetail DataConnectorError (errorResponseSummary e) (_crDetails e)
|
||||
|
||||
-- | This function assumes that if a type name is present in the custom object types for the table then it
|
||||
-- refers to a nested object of that type.
|
||||
-- Otherwise it is a normal (scalar) column.
|
||||
columnInfoToFieldInfo' :: HashMap G.Name (RQL.T.T.TableObjectType 'DataConnector) -> RQL.T.C.ColumnInfo 'DataConnector -> RQL.T.T.FieldInfo 'DataConnector
|
||||
columnInfoToFieldInfo' gqlTypes columnInfo@RQL.T.C.ColumnInfo {..} =
|
||||
maybe (RQL.T.T.FIColumn columnInfo) RQL.T.T.FINestedObject getNestedObjectInfo
|
||||
where
|
||||
getNestedObjectInfo =
|
||||
case ciType of
|
||||
RQL.T.C.ColumnScalar (DC.ScalarType scalarTypeName _) -> do
|
||||
gqlName <- GQL.mkName scalarTypeName
|
||||
guard $ HashMap.member gqlName gqlTypes
|
||||
pure $
|
||||
RQL.T.C.NestedObjectInfo
|
||||
{ RQL.T.C._noiSupportsNestedObjects = (),
|
||||
RQL.T.C._noiColumn = ciColumn,
|
||||
RQL.T.C._noiName = ciName,
|
||||
RQL.T.C._noiType = gqlName,
|
||||
RQL.T.C._noiIsNullable = ciIsNullable,
|
||||
RQL.T.C._noiDescription = ciDescription,
|
||||
RQL.T.C._noiMutability = ciMutability
|
||||
}
|
||||
RQL.T.C.ColumnEnumReference {} -> Nothing
|
||||
|
||||
supportsBeingRemoteRelationshipTarget' :: DC.SourceConfig -> Bool
|
||||
supportsBeingRemoteRelationshipTarget' DC.SourceConfig {..} =
|
||||
isJust $ API._qcForeach =<< API._cQueries _scCapabilities
|
||||
|
@ -326,6 +326,9 @@ translateAnnField sessionVariables sourceTableName = \case
|
||||
-- and add them back to the response JSON when we reshape what the agent returns
|
||||
-- to us
|
||||
pure Nothing
|
||||
AFNestedObject nestedObj ->
|
||||
Just . API.NestedObjField (Witch.from $ _anosColumn nestedObj)
|
||||
<$> translateNestedObjectSelect sessionVariables sourceTableName nestedObj
|
||||
|
||||
translateArrayRelationSelect ::
|
||||
( Has TableRelationships writerOutput,
|
||||
@ -433,6 +436,28 @@ translateSingleColumnAggregateFunction functionName =
|
||||
fmap API.SingleColumnAggregateFunction (G.mkName functionName)
|
||||
`onNothing` throw500 ("translateSingleColumnAggregateFunction: Invalid aggregate function encountered: " <> functionName)
|
||||
|
||||
translateNestedObjectSelect ::
|
||||
( Has TableRelationships writerOutput,
|
||||
Monoid writerOutput,
|
||||
MonadError QErr m
|
||||
) =>
|
||||
SessionVariables ->
|
||||
API.TableName ->
|
||||
AnnNestedObjectSelectG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
|
||||
CPS.WriterT writerOutput m API.Query
|
||||
translateNestedObjectSelect sessionVariables tableName selectG = do
|
||||
FieldsAndAggregates {..} <- translateAnnFieldsWithNoAggregates sessionVariables noPrefix tableName $ _anosFields selectG
|
||||
pure
|
||||
API.Query
|
||||
{ _qFields = mapFieldNameHashMap <$> _faaFields,
|
||||
_qAggregates = Nothing,
|
||||
_qAggregatesLimit = Nothing,
|
||||
_qLimit = Nothing,
|
||||
_qOffset = Nothing,
|
||||
_qWhere = Nothing,
|
||||
_qOrderBy = Nothing
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- | Validate if a 'API.QueryRequest' contains any relationships.
|
||||
@ -564,6 +589,12 @@ reshapeField field responseFieldValue =
|
||||
AFArrayRelation (ASAggregate aggregateArrayRelationField) ->
|
||||
reshapeAnnRelationSelect reshapeTableAggregateFields aggregateArrayRelationField =<< responseFieldValue
|
||||
AFExpression txt -> pure $ JE.text txt
|
||||
AFNestedObject nestedObj -> do
|
||||
nestedObjectFieldValue <- API.deserializeAsNestedObjFieldValue <$> responseFieldValue
|
||||
case nestedObjectFieldValue of
|
||||
Left err -> throw500 $ "Expected object in field returned by Data Connector agent: " <> err -- TODO(dmoverton): Add pathing information for error clarity
|
||||
Right nestedResponse ->
|
||||
reshapeAnnFields noPrefix (_anosFields nestedObj) nestedResponse
|
||||
|
||||
reshapeAnnRelationSelect ::
|
||||
MonadError QErr m =>
|
||||
|
@ -155,6 +155,7 @@ transformTable tableInfo =
|
||||
Nothing -- no views, only tables
|
||||
Nothing -- no description
|
||||
identityColumns
|
||||
mempty
|
||||
)
|
||||
|
||||
transformColumn ::
|
||||
|
@ -101,7 +101,8 @@ mergeMetadata InformationSchema {..} =
|
||||
else HS.empty,
|
||||
_ptmiViewInfo = Nothing,
|
||||
_ptmiDescription = Nothing,
|
||||
_ptmiExtraTableMetadata = ()
|
||||
_ptmiExtraTableMetadata = (),
|
||||
_ptmiCustomObjectTypes = mempty
|
||||
}
|
||||
|
||||
mergeDBTableMetadata :: DBTableMetadata 'MySQL -> DBTableMetadata 'MySQL -> DBTableMetadata 'MySQL
|
||||
@ -114,7 +115,8 @@ mergeDBTableMetadata new existing =
|
||||
_ptmiForeignKeys = _ptmiForeignKeys existing <> _ptmiForeignKeys new, -- union
|
||||
_ptmiViewInfo = _ptmiViewInfo existing <|> _ptmiViewInfo new,
|
||||
_ptmiDescription = _ptmiDescription existing <|> _ptmiDescription new,
|
||||
_ptmiExtraTableMetadata = ()
|
||||
_ptmiExtraTableMetadata = (),
|
||||
_ptmiCustomObjectTypes = mempty
|
||||
}
|
||||
|
||||
data InformationSchema = InformationSchema
|
||||
|
@ -249,6 +249,12 @@ transformObjectSelect ::
|
||||
Collector (AnnObjectSelectG b Void (UnpreparedValue b))
|
||||
transformObjectSelect = traverseOf aosFields transformAnnFields
|
||||
|
||||
transformNestedObjectSelect ::
|
||||
Backend b =>
|
||||
AnnNestedObjectSelectG b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b) ->
|
||||
Collector (AnnNestedObjectSelectG b Void (UnpreparedValue b))
|
||||
transformNestedObjectSelect = traverseOf anosFields transformAnnFields
|
||||
|
||||
transformGraphQLField ::
|
||||
GraphQLField (RemoteRelationshipField UnpreparedValue) var ->
|
||||
Collector (GraphQLField Void var)
|
||||
@ -325,6 +331,8 @@ transformAnnFields fields = do
|
||||
remoteAnnPlaceholder,
|
||||
Just $ createRemoteJoin (Map.intersection joinColumnAliases _rrsLHSJoinFields) _rrsRelationship
|
||||
)
|
||||
AFNestedObject nestedObj ->
|
||||
(,Nothing) . AFNestedObject <$> transformNestedObjectSelect nestedObj
|
||||
|
||||
let transformedFields = (fmap . fmap) fst annotatedFields
|
||||
remoteJoins =
|
||||
|
@ -41,7 +41,6 @@ import Hasura.RQL.Types.Column
|
||||
import Hasura.RQL.Types.Common
|
||||
import Hasura.RQL.Types.CustomTypes
|
||||
import Hasura.RQL.Types.Relationships.Remote
|
||||
import Hasura.RQL.Types.Table
|
||||
import Hasura.SQL.AnyBackend qualified as AB
|
||||
import Hasura.SQL.Backend
|
||||
import Hasura.Session
|
||||
@ -337,7 +336,7 @@ actionOutputFields outputType annotatedObject objectTypes = do
|
||||
_rsfiType = _atrType,
|
||||
_rsfiSource = _atrSource,
|
||||
_rsfiSourceConfig = _atrSourceConfig,
|
||||
_rsfiTable = tableInfoName _atrTableInfo,
|
||||
_rsfiTable = _atrTableName,
|
||||
_rsfiMapping = joinMapping
|
||||
}
|
||||
}
|
||||
|
@ -161,6 +161,7 @@ boolExpInternal gqlName fieldInfos description memoizeKey mkAggPredParser = do
|
||||
|
||||
-- Using remote relationship fields in boolean expressions is not supported.
|
||||
FIRemoteRelationship _ -> empty
|
||||
FINestedObject _ -> empty -- TODO(dmoverton)
|
||||
|
||||
-- |
|
||||
-- > input type_bool_exp {
|
||||
|
@ -23,6 +23,7 @@ module Hasura.GraphQL.Schema.Common
|
||||
ConnectionSelectExp,
|
||||
AnnotatedActionField,
|
||||
AnnotatedActionFields,
|
||||
AnnotatedNestedObjectSelect,
|
||||
EdgeFields,
|
||||
Scenario (..),
|
||||
SelectArgs,
|
||||
@ -311,6 +312,8 @@ type AnnotatedActionFields = IR.ActionFieldsG (IR.RemoteRelationshipField IR.Unp
|
||||
|
||||
type AnnotatedActionField = IR.ActionFieldG (IR.RemoteRelationshipField IR.UnpreparedValue)
|
||||
|
||||
type AnnotatedNestedObjectSelect b = IR.AnnNestedObjectSelectG b (IR.RemoteRelationshipField IR.UnpreparedValue) (IR.UnpreparedValue b)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
data RemoteSchemaParser n = RemoteSchemaParser
|
||||
|
@ -211,6 +211,7 @@ tableFieldsInput tableInfo = do
|
||||
mkFieldParser = \case
|
||||
FIComputedField _ -> pure Nothing
|
||||
FIRemoteRelationship _ -> pure Nothing
|
||||
FINestedObject _ -> pure Nothing -- TODO(dmoverton)
|
||||
FIColumn columnInfo -> do
|
||||
if (_cmIsInsertable $ ciMutability columnInfo)
|
||||
then mkColumnParser columnInfo
|
||||
|
@ -180,6 +180,7 @@ orderByExpInternal gqlName description tableFields memoizeKey = do
|
||||
aggregationOrderBy
|
||||
ReturnsOthers -> empty
|
||||
FIRemoteRelationship _ -> empty
|
||||
FINestedObject _ -> empty -- TODO(dmoverton)
|
||||
|
||||
-- | Corresponds to an object type for an order by.
|
||||
--
|
||||
|
@ -49,7 +49,7 @@ import Hasura.Base.Error
|
||||
import Hasura.Base.ErrorMessage (toErrorMessage)
|
||||
import Hasura.CustomReturnType.Cache (CustomReturnTypeInfo (..))
|
||||
import Hasura.GraphQL.Parser.Class
|
||||
import Hasura.GraphQL.Parser.Internal.Parser qualified as P
|
||||
import Hasura.GraphQL.Parser.Internal.Parser qualified as IP
|
||||
import Hasura.GraphQL.Schema.Backend
|
||||
import Hasura.GraphQL.Schema.BoolExp
|
||||
import Hasura.GraphQL.Schema.Common
|
||||
@ -75,6 +75,7 @@ import Hasura.RQL.Types.Backend
|
||||
import Hasura.RQL.Types.Column
|
||||
import Hasura.RQL.Types.Common
|
||||
import Hasura.RQL.Types.ComputedField
|
||||
import Hasura.RQL.Types.CustomTypes
|
||||
import Hasura.RQL.Types.Metadata.Object
|
||||
import Hasura.RQL.Types.Permission qualified as Permission
|
||||
import Hasura.RQL.Types.Relationships.Local
|
||||
@ -460,7 +461,7 @@ defaultTableSelectionSet tableInfo = runMaybeT do
|
||||
<&> parsedSelectionsToFields IR.AFExpression
|
||||
where
|
||||
selectionSetObjectWithDirective name description parsers implementsInterfaces directives =
|
||||
P.setParserDirectives directives $
|
||||
IP.setParserDirectives directives $
|
||||
P.selectionSetObject name description parsers implementsInterfaces
|
||||
|
||||
-- | List of table fields object.
|
||||
@ -1276,6 +1277,8 @@ fieldSelection table tableInfo = \case
|
||||
pure $!
|
||||
P.selection fieldName (ciDescription columnInfo) pathArg field
|
||||
<&> IR.mkAnnColumnField (ciColumn columnInfo) (ciType columnInfo) caseBoolExpUnpreparedValue
|
||||
FINestedObject nestedObjectInfo ->
|
||||
pure . fmap IR.AFNestedObject <$> nestedObjectFieldParser tableInfo nestedObjectInfo
|
||||
FIRelationship relationshipInfo ->
|
||||
concat . maybeToList <$> relationshipField table relationshipInfo
|
||||
FIComputedField computedFieldInfo ->
|
||||
@ -1295,6 +1298,58 @@ fieldSelection table tableInfo = \case
|
||||
relationshipFields <- fromMaybe [] <$> remoteRelationshipField remoteFieldInfo
|
||||
let lhsFields = _rfiLHS remoteFieldInfo
|
||||
pure $ map (fmap (IR.AFRemote . IR.RemoteRelationshipSelect lhsFields)) relationshipFields
|
||||
where
|
||||
nestedObjectFieldParser :: TableInfo b -> NestedObjectInfo b -> SchemaT r m (FieldParser n (AnnotatedNestedObjectSelect b))
|
||||
nestedObjectFieldParser TableInfo {..} NestedObjectInfo {..} = do
|
||||
let customObjectTypes = _tciCustomObjectTypes _tiCoreInfo
|
||||
case Map.lookup _noiType customObjectTypes of
|
||||
Just objectType -> do
|
||||
parser <- nestedObjectParser _noiSupportsNestedObjects customObjectTypes objectType _noiColumn _noiIsNullable
|
||||
pure $ P.subselection_ _noiName _noiDescription parser
|
||||
_ -> throw500 $ "fieldSelection: object type " <> _noiType <<> " not found"
|
||||
|
||||
nestedObjectParser ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
XNestedObjects b ->
|
||||
HashMap G.Name (TableObjectType b) ->
|
||||
TableObjectType b ->
|
||||
Column b ->
|
||||
Bool ->
|
||||
SchemaT r m (P.Parser 'Output n (AnnotatedNestedObjectSelect b))
|
||||
nestedObjectParser supportsNestedObjects objectTypes objectType column isNullable = do
|
||||
allFieldParsers <- for (toList $ _totFields objectType) outputFieldParser
|
||||
pure $
|
||||
outputParserModifier isNullable $
|
||||
P.selectionSet (_totName objectType) (_totDescription objectType) allFieldParsers
|
||||
<&> IR.AnnNestedObjectSelectG supportsNestedObjects column . parsedSelectionsToFields IR.AFExpression
|
||||
where
|
||||
outputParserModifier True = P.nullableParser
|
||||
outputParserModifier False = P.nonNullableParser
|
||||
|
||||
outputFieldParser ::
|
||||
TableObjectFieldDefinition b ->
|
||||
SchemaT r m (IP.FieldParser MetadataObjId n (IR.AnnFieldG b (IR.RemoteRelationshipField IR.UnpreparedValue) (IR.UnpreparedValue b)))
|
||||
outputFieldParser (TableObjectFieldDefinition column' name description (GraphQLType gType) objectFieldType) =
|
||||
P.memoizeOn 'nestedObjectParser (_totName objectType, name) do
|
||||
case objectFieldType of
|
||||
TOFTScalar fieldTypeName scalarType ->
|
||||
wrapScalar scalarType $ customScalarParser fieldTypeName
|
||||
TOFTObject objectName -> do
|
||||
objectType' <- Map.lookup objectName objectTypes `onNothing` throw500 ("Custom type " <> objectName <<> " not found")
|
||||
parser <- fmap (IR.AFNestedObject @b) <$> nestedObjectParser supportsNestedObjects objectTypes objectType' column' (G.isNullable gType)
|
||||
pure $ P.subselection_ name description parser
|
||||
where
|
||||
wrapScalar scalarType parser =
|
||||
pure $
|
||||
P.wrapFieldParser gType (P.selection_ name description parser)
|
||||
$> IR.mkAnnColumnField column' (ColumnScalar scalarType) Nothing Nothing
|
||||
customScalarParser fieldTypeName =
|
||||
let schemaType = P.TNamed P.NonNullable $ P.Definition fieldTypeName Nothing Nothing [] P.TIScalar
|
||||
in P.Parser
|
||||
{ pType = schemaType,
|
||||
pParser = P.valueToJSON (P.toGraphQLType schemaType)
|
||||
}
|
||||
|
||||
{- Note [Permission filter deduplication]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -1471,7 +1526,7 @@ relationshipField table ri = runMaybeT do
|
||||
_ -> pure Nullable
|
||||
pure $
|
||||
pure $
|
||||
case nullable of { Nullable -> id; NotNullable -> P.nonNullableField } $
|
||||
case nullable of { Nullable -> id; NotNullable -> IP.nonNullableField } $
|
||||
P.subselection_ relFieldName desc selectionSetParser
|
||||
<&> \fields ->
|
||||
IR.AFObjectRelation $
|
||||
|
@ -229,6 +229,8 @@ tableSelectFields tableInfo = do
|
||||
canBeSelected _ Nothing _ = pure False
|
||||
canBeSelected _ (Just permissions) (FIColumn columnInfo) =
|
||||
pure $! Map.member (ciColumn columnInfo) (spiCols permissions)
|
||||
canBeSelected _ (Just permissions) (FINestedObject NestedObjectInfo {..}) =
|
||||
pure $! Map.member _noiColumn (spiCols permissions)
|
||||
canBeSelected role _ (FIRelationship relationshipInfo) = do
|
||||
tableInfo' <- askTableInfo $ riRTable relationshipInfo
|
||||
pure $! isJust $ tableSelectPermissions @b role tableInfo'
|
||||
|
@ -318,7 +318,7 @@ validateCustomTypeDefinitions sources customTypes allScalars = do
|
||||
_trdType
|
||||
_siName
|
||||
_siConfiguration
|
||||
remoteTableInfo
|
||||
(tableInfoName remoteTableInfo)
|
||||
annotatedFieldMapping
|
||||
|
||||
pure $
|
||||
|
@ -178,6 +178,8 @@ annColExp rhsParser rootFieldInfoMap colInfoMap (ColExp fieldName colVal) = do
|
||||
colInfo <- askFieldInfo colInfoMap fieldName
|
||||
case colInfo of
|
||||
FIColumn pgi -> AVColumn pgi <$> parseBoolExpOperations (_berpValueParser rhsParser) rootFieldInfoMap colInfoMap (ColumnReferenceColumn pgi) colVal
|
||||
FINestedObject {} ->
|
||||
throw400 NotSupported "nested object not supported"
|
||||
FIRelationship relInfo -> do
|
||||
relBoolExp <- decodeValue colVal
|
||||
relFieldInfoMap <- askFieldInfoMapSource $ riRTable relInfo
|
||||
|
@ -666,7 +666,8 @@ buildSchemaCacheRule logger env = proc (MetadataWithResourceVersion metadataNoDe
|
||||
interpretWriter
|
||||
-< for (tablesRawInfo `alignTableMap` nonColumnsByTable) \(tableRawInfo, nonColumnInput) -> do
|
||||
let columns = _tciFieldInfoMap tableRawInfo
|
||||
allFields :: FieldInfoMap (FieldInfo b) <- addNonColumnFields allSources sourceName tablesRawInfo columns remoteSchemaMap dbFunctions nonColumnInput
|
||||
customObjectTypes = _tciCustomObjectTypes tableRawInfo
|
||||
allFields :: FieldInfoMap (FieldInfo b) <- addNonColumnFields allSources sourceName customObjectTypes tablesRawInfo columns remoteSchemaMap dbFunctions nonColumnInput
|
||||
pure $ tableRawInfo {_tciFieldInfoMap = allFields}
|
||||
|
||||
-- permissions
|
||||
|
@ -38,25 +38,26 @@ addNonColumnFields ::
|
||||
) =>
|
||||
HashMap SourceName (AB.AnyBackend PartiallyResolvedSource) ->
|
||||
SourceName ->
|
||||
HashMap G.Name (TableObjectType b) ->
|
||||
HashMap (TableName b) (TableCoreInfoG b (ColumnInfo b) (ColumnInfo b)) ->
|
||||
FieldInfoMap (ColumnInfo b) ->
|
||||
PartiallyResolvedRemoteSchemaMap ->
|
||||
DBFunctionsMetadata b ->
|
||||
NonColumnTableInputs b ->
|
||||
m (FieldInfoMap (FieldInfo b))
|
||||
addNonColumnFields allSources source rawTableInfo columns remoteSchemaMap pgFunctions NonColumnTableInputs {..} = do
|
||||
addNonColumnFields allSources source customObjectTypes rawTableInfos columns remoteSchemaMap pgFunctions NonColumnTableInputs {..} = do
|
||||
objectRelationshipInfos <-
|
||||
buildInfoMapPreservingMetadataM
|
||||
_rdName
|
||||
(mkRelationshipMetadataObject @b ObjRel source _nctiTable)
|
||||
(buildObjectRelationship (_tciForeignKeys <$> rawTableInfo) source _nctiTable)
|
||||
(buildObjectRelationship (_tciForeignKeys <$> rawTableInfos) source _nctiTable)
|
||||
_nctiObjectRelationships
|
||||
|
||||
arrayRelationshipInfos <-
|
||||
buildInfoMapPreservingMetadataM
|
||||
_rdName
|
||||
(mkRelationshipMetadataObject @b ArrRel source _nctiTable)
|
||||
(buildArrayRelationship (_tciForeignKeys <$> rawTableInfo) source _nctiTable)
|
||||
(buildArrayRelationship (_tciForeignKeys <$> rawTableInfos) source _nctiTable)
|
||||
_nctiArrayRelationships
|
||||
|
||||
let relationshipInfos = objectRelationshipInfos <> arrayRelationshipInfos
|
||||
@ -65,7 +66,7 @@ addNonColumnFields allSources source rawTableInfo columns remoteSchemaMap pgFunc
|
||||
buildInfoMapPreservingMetadataM
|
||||
_cfmName
|
||||
(mkComputedFieldMetadataObject source _nctiTable)
|
||||
(buildComputedField (HS.fromList $ M.keys rawTableInfo) (HS.fromList $ map ciColumn $ M.elems columns) source pgFunctions _nctiTable)
|
||||
(buildComputedField (HS.fromList $ M.keys rawTableInfos) (HS.fromList $ map ciColumn $ M.elems columns) source pgFunctions _nctiTable)
|
||||
_nctiComputedFields
|
||||
-- the fields that can be used for defining join conditions to other sources/remote schemas:
|
||||
-- 1. all columns
|
||||
@ -151,7 +152,7 @@ addNonColumnFields allSources source rawTableInfo columns remoteSchemaMap pgFunc
|
||||
return (fieldInfo, metadata)
|
||||
|
||||
noColumnConflicts = \case
|
||||
This columnInfo -> pure $ FIColumn columnInfo
|
||||
This columnInfo -> pure $ columnInfoToFieldInfo customObjectTypes columnInfo
|
||||
That (fieldInfo, _) -> pure $ fieldInfo
|
||||
These columnInfo (_, fieldMetadata) -> do
|
||||
recordInconsistencyM Nothing fieldMetadata "field definition conflicts with postgres column"
|
||||
|
@ -482,6 +482,7 @@ updateColExp qt rf (ColExp fld val) =
|
||||
Nothing -> pure val
|
||||
Just fi -> case fi of
|
||||
FIColumn _ -> pure val
|
||||
FINestedObject _ -> pure val
|
||||
FIComputedField _ -> pure val
|
||||
FIRelationship ri -> do
|
||||
let remTable = riRTable ri
|
||||
|
@ -518,7 +518,8 @@ buildTableCache = Inc.cache proc (source, sourceConfig, dbTablesMeta, tableBuild
|
||||
_tciCustomConfig = config,
|
||||
_tciDescription = description,
|
||||
_tciExtraTableMetadata = _ptmiExtraTableMetadata metadataTable,
|
||||
_tciApolloFederationConfig = apolloFedConfig
|
||||
_tciApolloFederationConfig = apolloFedConfig,
|
||||
_tciCustomObjectTypes = fromMaybe mempty $ _ptmiCustomObjectTypes metadataTable
|
||||
}
|
||||
|
||||
-- Step 2: Process the raw table cache to replace Postgres column types with logical column
|
||||
|
@ -36,6 +36,8 @@ module Hasura.RQL.IR.Select
|
||||
AnnFieldG (..),
|
||||
AnnFields,
|
||||
AnnFieldsG,
|
||||
AnnNestedObjectSelectG (..),
|
||||
AnnNestedObjectSelect,
|
||||
AnnObjectSelect,
|
||||
AnnObjectSelectG (..),
|
||||
AnnRelationSelectG (..),
|
||||
@ -96,6 +98,9 @@ module Hasura.RQL.IR.Select
|
||||
aarRelationshipName,
|
||||
aarColumnMapping,
|
||||
aarAnnSelect,
|
||||
anosSupportsNestedObjects,
|
||||
anosColumn,
|
||||
anosFields,
|
||||
aosFields,
|
||||
aosTableFrom,
|
||||
aosTableFilter,
|
||||
@ -648,6 +653,9 @@ data AnnFieldG (b :: BackendType) (r :: Type) v
|
||||
AFRemote (RemoteRelationshipSelect b r)
|
||||
| AFNodeId (XRelay b) SourceName (TableName b) (PrimaryKeyColumns b)
|
||||
| AFExpression Text
|
||||
| -- | Nested object.
|
||||
AFNestedObject (AnnNestedObjectSelectG b r v)
|
||||
-- TODO (dmoverton): add AFNestedArray
|
||||
deriving stock (Functor, Foldable, Traversable)
|
||||
|
||||
deriving stock instance
|
||||
@ -656,7 +664,8 @@ deriving stock instance
|
||||
Eq (ArraySelectG b r v),
|
||||
Eq (ComputedFieldSelect b r v),
|
||||
Eq (ObjectRelationSelectG b r v),
|
||||
Eq (RemoteRelationshipSelect b r)
|
||||
Eq (RemoteRelationshipSelect b r),
|
||||
Eq (AnnNestedObjectSelectG b r v)
|
||||
) =>
|
||||
Eq (AnnFieldG b r v)
|
||||
|
||||
@ -666,7 +675,8 @@ deriving stock instance
|
||||
Show (ArraySelectG b r v),
|
||||
Show (ComputedFieldSelect b r v),
|
||||
Show (ObjectRelationSelectG b r v),
|
||||
Show (RemoteRelationshipSelect b r)
|
||||
Show (RemoteRelationshipSelect b r),
|
||||
Show (AnnNestedObjectSelectG b r v)
|
||||
) =>
|
||||
Show (AnnFieldG b r v)
|
||||
|
||||
@ -679,6 +689,7 @@ instance Backend b => Bifoldable (AnnFieldG b) where
|
||||
AFRemote r -> foldMap f r
|
||||
AFNodeId {} -> mempty
|
||||
AFExpression {} -> mempty
|
||||
AFNestedObject no -> bifoldMap f g no
|
||||
|
||||
type AnnField b = AnnFieldG b Void (SQLExpression b)
|
||||
|
||||
@ -1059,6 +1070,33 @@ deriving stock instance
|
||||
) =>
|
||||
Show (RemoteSourceSelect r vf tgt)
|
||||
|
||||
-- Nested objects
|
||||
|
||||
data AnnNestedObjectSelectG (b :: BackendType) (r :: Type) v = AnnNestedObjectSelectG
|
||||
{ _anosSupportsNestedObjects :: XNestedObjects b,
|
||||
_anosColumn :: Column b,
|
||||
_anosFields :: AnnFieldsG b r v
|
||||
}
|
||||
deriving stock (Functor, Foldable, Traversable)
|
||||
|
||||
deriving stock instance
|
||||
( Backend b,
|
||||
Eq (AnnFieldsG b r v)
|
||||
) =>
|
||||
Eq (AnnNestedObjectSelectG b r v)
|
||||
|
||||
deriving stock instance
|
||||
( Backend b,
|
||||
Show (AnnFieldsG b r v)
|
||||
) =>
|
||||
Show (AnnNestedObjectSelectG b r v)
|
||||
|
||||
instance Backend b => Bifoldable (AnnNestedObjectSelectG b) where
|
||||
bifoldMap f g AnnNestedObjectSelectG {..} =
|
||||
foldMap (foldMap $ bifoldMap f g) _anosFields
|
||||
|
||||
type AnnNestedObjectSelect b r = AnnNestedObjectSelectG b r (SQLExpression b)
|
||||
|
||||
-- Permissions
|
||||
|
||||
data TablePermG (b :: BackendType) v = TablePerm
|
||||
@ -1121,6 +1159,7 @@ data CountDistinct
|
||||
|
||||
$(makeLenses ''AnnSelectG)
|
||||
$(makeLenses ''AnnObjectSelectG)
|
||||
$(makeLenses ''AnnNestedObjectSelectG)
|
||||
$(makeLenses ''AnnRelationSelectG)
|
||||
$(makeLenses ''ConnectionSelect)
|
||||
$(makeLenses ''SelectArgsG)
|
||||
|
@ -161,6 +161,13 @@ class
|
||||
Show (XRelay b),
|
||||
Eq (XStreamingSubscription b),
|
||||
Show (XStreamingSubscription b),
|
||||
Eq (XNestedObjects b),
|
||||
Ord (XNestedObjects b),
|
||||
Show (XNestedObjects b),
|
||||
NFData (XNestedObjects b),
|
||||
Hashable (XNestedObjects b),
|
||||
ToJSON (XNestedObjects b),
|
||||
ToTxt (XNestedObjects b),
|
||||
-- Intermediate Representations
|
||||
Traversable (BooleanOperators b),
|
||||
Traversable (UpdateVariant b),
|
||||
@ -302,6 +309,9 @@ class
|
||||
|
||||
type XStreamingSubscription b :: Type
|
||||
|
||||
type XNestedObjects b :: Type
|
||||
type XNestedObjects b = XDisable
|
||||
|
||||
-- The result of dynamic connection template resolution
|
||||
type ResolvedConnectionTemplate b :: Type
|
||||
type ResolvedConnectionTemplate b = () -- Uninmplemented value
|
||||
|
@ -16,6 +16,7 @@ module Hasura.RQL.Types.Column
|
||||
ColumnValue (..),
|
||||
ColumnMutability (..),
|
||||
ColumnInfo (..),
|
||||
NestedObjectInfo (..),
|
||||
RawColumnInfo (..),
|
||||
PrimaryKeyColumns,
|
||||
getColInfos,
|
||||
@ -249,6 +250,31 @@ instance Backend b => ToJSON (ColumnInfo b) where
|
||||
toJSON = genericToJSON hasuraJSON
|
||||
toEncoding = genericToEncoding hasuraJSON
|
||||
|
||||
data NestedObjectInfo b = NestedObjectInfo
|
||||
{ _noiSupportsNestedObjects :: XNestedObjects b,
|
||||
_noiColumn :: Column b,
|
||||
_noiName :: G.Name,
|
||||
_noiType :: G.Name,
|
||||
_noiIsNullable :: Bool,
|
||||
_noiDescription :: Maybe G.Description,
|
||||
_noiMutability :: ColumnMutability
|
||||
}
|
||||
deriving (Generic)
|
||||
|
||||
deriving instance (Backend b) => Eq (NestedObjectInfo b)
|
||||
|
||||
deriving instance (Backend b) => Ord (NestedObjectInfo b)
|
||||
|
||||
deriving instance (Backend b) => Show (NestedObjectInfo b)
|
||||
|
||||
instance (Backend b) => NFData (NestedObjectInfo b)
|
||||
|
||||
instance (Backend b) => Hashable (NestedObjectInfo b)
|
||||
|
||||
instance (Backend b) => ToJSON (NestedObjectInfo b) where
|
||||
toJSON = genericToJSON hasuraJSON
|
||||
toEncoding = genericToEncoding hasuraJSON
|
||||
|
||||
type PrimaryKeyColumns b = NESeq (ColumnInfo b)
|
||||
|
||||
onlyNumCols :: forall b. Backend b => [ColumnInfo b] -> [ColumnInfo b]
|
||||
|
@ -45,7 +45,7 @@ module Hasura.RQL.Types.CustomTypes
|
||||
)
|
||||
where
|
||||
|
||||
import Autodocodec (HasCodec (codec), bimapCodec, dimapCodec, optionalField', optionalFieldWith', optionalFieldWithDefault', optionalFieldWithOmittedDefault', requiredField', requiredFieldWith')
|
||||
import Autodocodec (HasCodec (codec), dimapCodec, optionalField', optionalFieldWith', optionalFieldWithDefault', optionalFieldWithOmittedDefault', requiredField', requiredFieldWith')
|
||||
import Autodocodec qualified as AC
|
||||
import Autodocodec.Extended (graphQLEnumValueCodec, graphQLFieldDescriptionCodec, graphQLFieldNameCodec, typeableName)
|
||||
import Control.Lens.TH (makeLenses)
|
||||
@ -54,7 +54,6 @@ import Data.Aeson qualified as J
|
||||
import Data.Aeson.TH qualified as J
|
||||
import Data.HashMap.Strict qualified as Map
|
||||
import Data.HashSet qualified as Set
|
||||
import Data.Text qualified as T
|
||||
import Data.Text.Extended (ToTxt (..))
|
||||
import Data.Typeable (Typeable)
|
||||
import Hasura.Backends.Postgres.Instances.Types ()
|
||||
@ -67,47 +66,11 @@ import Hasura.RQL.Types.Common
|
||||
import Hasura.RQL.Types.Table
|
||||
import Hasura.SQL.AnyBackend
|
||||
import Hasura.SQL.Backend
|
||||
import Language.GraphQL.Draft.Parser qualified as GParse
|
||||
import Language.GraphQL.Draft.Printer qualified as GPrint
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
import Text.Builder qualified as T
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Metadata
|
||||
|
||||
-- | A wrapper around 'G.GType' which allows us to define custom JSON
|
||||
-- instances.
|
||||
--
|
||||
-- TODO: this name is ambiguous, and conflicts with
|
||||
-- Hasura.RQL.DDL.RemoteSchema.Permission.GraphQLType; it should perhaps be
|
||||
-- renamed, made internal to this module, or removed altogether?
|
||||
newtype GraphQLType = GraphQLType {unGraphQLType :: G.GType}
|
||||
deriving (Show, Eq, Ord, Generic, NFData)
|
||||
|
||||
instance HasCodec GraphQLType where
|
||||
codec = bimapCodec dec enc codec
|
||||
where
|
||||
dec t = case GParse.parseGraphQLType t of
|
||||
Left _ -> Left $ "not a valid GraphQL type: " <> T.unpack t
|
||||
Right a -> Right $ GraphQLType a
|
||||
enc = T.run . GPrint.graphQLType . unGraphQLType
|
||||
|
||||
instance J.ToJSON GraphQLType where
|
||||
toJSON = J.toJSON . T.run . GPrint.graphQLType . unGraphQLType
|
||||
|
||||
instance J.FromJSON GraphQLType where
|
||||
parseJSON =
|
||||
J.withText "GraphQLType" $ \t ->
|
||||
case GParse.parseGraphQLType t of
|
||||
Left _ -> fail $ "not a valid GraphQL type: " <> T.unpack t
|
||||
Right a -> return $ GraphQLType a
|
||||
|
||||
isListType :: GraphQLType -> Bool
|
||||
isListType = coerce G.isListType
|
||||
|
||||
isNullableType :: GraphQLType -> Bool
|
||||
isNullableType = coerce G.isNullable
|
||||
|
||||
isInBuiltScalar :: Text -> Bool
|
||||
isInBuiltScalar s
|
||||
| s == G.unName GName._Int = True
|
||||
@ -418,7 +381,7 @@ data AnnotatedTypeRelationship = AnnotatedTypeRelationship
|
||||
_atrSource :: SourceName,
|
||||
_atrSourceConfig :: SourceConfig ('Postgres 'Vanilla),
|
||||
-- TODO: see comment in 'TypeRelationship'
|
||||
_atrTableInfo :: TableInfo ('Postgres 'Vanilla),
|
||||
_atrTableName :: TableName ('Postgres 'Vanilla),
|
||||
_atrFieldMapping :: HashMap ObjectFieldName (ColumnInfo ('Postgres 'Vanilla))
|
||||
}
|
||||
deriving (Generic)
|
||||
|
@ -34,6 +34,7 @@ import Hasura.SQL.Backend
|
||||
import Hasura.SQL.Types
|
||||
import Hasura.Server.Migrate.Version
|
||||
import Hasura.Services.Network
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
import Network.HTTP.Client qualified as HTTP
|
||||
|
||||
class
|
||||
@ -208,6 +209,15 @@ class
|
||||
validateLogicalModel _ _ _ _ =
|
||||
throw500 "validateLogicalModel: not implemented for this backend."
|
||||
|
||||
-- | How to convert a column to a field.
|
||||
-- For backends that don't support nested objects or arrays the default implementation
|
||||
-- (i.e. wrapping the ColumnInfo in FIColumn) is what you want.
|
||||
columnInfoToFieldInfo ::
|
||||
HashMap G.Name (TableObjectType b) ->
|
||||
ColumnInfo b ->
|
||||
FieldInfo b
|
||||
columnInfoToFieldInfo _ = FIColumn
|
||||
|
||||
-- | Allows the backend to control whether or not a particular source supports being
|
||||
-- the target of remote relationships or not
|
||||
supportsBeingRemoteRelationshipTarget :: SourceConfig b -> Bool
|
||||
|
@ -13,6 +13,7 @@ module Hasura.RQL.Types.Table
|
||||
FieldInfoMap,
|
||||
ForeignKey (..),
|
||||
ForeignKeyMetadata (..),
|
||||
GraphQLType (..),
|
||||
InsPermInfo (..),
|
||||
PrimaryKey (..),
|
||||
RolePermInfo (..),
|
||||
@ -26,6 +27,9 @@ module Hasura.RQL.Types.Table
|
||||
TableCoreInfoG (..),
|
||||
TableCustomRootFields (..),
|
||||
TableInfo (..),
|
||||
TableObjectType (..),
|
||||
TableObjectFieldDefinition (..),
|
||||
TableObjectFieldType (..),
|
||||
UniqueConstraint (..),
|
||||
UpdPermInfo (..),
|
||||
ViewInfo (..),
|
||||
@ -49,7 +53,9 @@ module Hasura.RQL.Types.Table
|
||||
getFieldInfoM,
|
||||
getRels,
|
||||
getRemoteFieldInfoName,
|
||||
isListType,
|
||||
isMutable,
|
||||
isNullableType,
|
||||
mkAdminRolePermInfo,
|
||||
permDel,
|
||||
permIns,
|
||||
@ -68,6 +74,7 @@ module Hasura.RQL.Types.Table
|
||||
tciCustomConfig,
|
||||
tciDescription,
|
||||
tciApolloFederationConfig,
|
||||
tciCustomObjectTypes,
|
||||
tciEnumValues,
|
||||
tciExtraTableMetadata,
|
||||
tciFieldInfoMap,
|
||||
@ -104,6 +111,7 @@ import Autodocodec
|
||||
import Autodocodec qualified as AC
|
||||
import Autodocodec.Extended (graphQLFieldNameCodec)
|
||||
import Control.Lens hiding ((.=))
|
||||
import Data.Aeson qualified as J
|
||||
import Data.Aeson.Casing
|
||||
import Data.Aeson.Extended
|
||||
import Data.Aeson.TH
|
||||
@ -136,7 +144,43 @@ import Hasura.SQL.AnyBackend (runBackend)
|
||||
import Hasura.SQL.Backend
|
||||
import Hasura.Server.Utils (englishList)
|
||||
import Hasura.Session
|
||||
import Language.GraphQL.Draft.Parser qualified as GParse
|
||||
import Language.GraphQL.Draft.Printer qualified as GPrint
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
import Text.Builder qualified as T
|
||||
|
||||
-- | A wrapper around 'G.GType' which allows us to define custom JSON
|
||||
-- instances.
|
||||
--
|
||||
-- TODO: this name is ambiguous, and conflicts with
|
||||
-- Hasura.RQL.DDL.RemoteSchema.Permission.GraphQLType; it should perhaps be
|
||||
-- renamed, made internal to this module, or removed altogether?
|
||||
newtype GraphQLType = GraphQLType {unGraphQLType :: G.GType}
|
||||
deriving (Show, Eq, Ord, Generic, NFData)
|
||||
|
||||
instance HasCodec GraphQLType where
|
||||
codec = AC.bimapCodec dec enc codec
|
||||
where
|
||||
dec t = case GParse.parseGraphQLType t of
|
||||
Left _ -> Left $ "not a valid GraphQL type: " <> T.unpack t
|
||||
Right a -> Right $ GraphQLType a
|
||||
enc = T.run . GPrint.graphQLType . unGraphQLType
|
||||
|
||||
instance J.ToJSON GraphQLType where
|
||||
toJSON = J.toJSON . T.run . GPrint.graphQLType . unGraphQLType
|
||||
|
||||
instance J.FromJSON GraphQLType where
|
||||
parseJSON =
|
||||
J.withText "GraphQLType" $ \t ->
|
||||
case GParse.parseGraphQLType t of
|
||||
Left _ -> fail $ "not a valid GraphQL type: " <> T.unpack t
|
||||
Right a -> return $ GraphQLType a
|
||||
|
||||
isListType :: GraphQLType -> Bool
|
||||
isListType = coerce G.isListType
|
||||
|
||||
isNullableType :: GraphQLType -> Bool
|
||||
isNullableType = coerce G.isNullable
|
||||
|
||||
data CustomRootField = CustomRootField
|
||||
{ _crfName :: Maybe G.Name,
|
||||
@ -300,6 +344,7 @@ getAllCustomRootFields TableCustomRootFields {..} =
|
||||
|
||||
data FieldInfo (b :: BackendType)
|
||||
= FIColumn (ColumnInfo b)
|
||||
| FINestedObject (NestedObjectInfo b)
|
||||
| FIRelationship (RelInfo b)
|
||||
| FIComputedField (ComputedFieldInfo b)
|
||||
| FIRemoteRelationship (RemoteFieldInfo (DBJoinField b))
|
||||
@ -322,6 +367,7 @@ type FieldInfoMap = M.HashMap FieldName
|
||||
fieldInfoName :: forall b. Backend b => FieldInfo b -> FieldName
|
||||
fieldInfoName = \case
|
||||
FIColumn info -> fromCol @b $ ciColumn info
|
||||
FINestedObject info -> fromCol @b $ _noiColumn info
|
||||
FIRelationship info -> fromRel $ riName info
|
||||
FIComputedField info -> fromComputedField $ _cfiName info
|
||||
FIRemoteRelationship info -> fromRemoteRelationship $ getRemoteFieldInfoName info
|
||||
@ -329,6 +375,7 @@ fieldInfoName = \case
|
||||
fieldInfoGraphQLName :: FieldInfo b -> Maybe G.Name
|
||||
fieldInfoGraphQLName = \case
|
||||
FIColumn info -> Just $ ciName info
|
||||
FINestedObject info -> Just $ _noiName info
|
||||
FIRelationship info -> G.mkName $ relNameToTxt $ riName info
|
||||
FIComputedField info -> G.mkName $ computedFieldNameToText $ _cfiName info
|
||||
FIRemoteRelationship info -> G.mkName $ relNameToTxt $ getRemoteFieldInfoName info
|
||||
@ -344,6 +391,7 @@ getRemoteFieldInfoName RemoteFieldInfo {_rfiRHS} = case _rfiRHS of
|
||||
fieldInfoGraphQLNames :: FieldInfo b -> [G.Name]
|
||||
fieldInfoGraphQLNames info = case info of
|
||||
FIColumn _ -> maybeToList $ fieldInfoGraphQLName info
|
||||
FINestedObject _ -> maybeToList $ fieldInfoGraphQLName info
|
||||
FIRelationship relationshipInfo -> fold do
|
||||
name <- fieldInfoGraphQLName info
|
||||
pure $ case riType relationshipInfo of
|
||||
@ -913,6 +961,63 @@ instance Backend b => ToJSON (ForeignKey b) where
|
||||
instance Backend b => FromJSON (ForeignKey b) where
|
||||
parseJSON = genericParseJSON hasuraJSON
|
||||
|
||||
data TableObjectType (b :: BackendType) = TableObjectType
|
||||
{ _totName :: G.Name,
|
||||
_totDescription :: Maybe G.Description,
|
||||
_totFields :: NonEmpty (TableObjectFieldDefinition b)
|
||||
}
|
||||
deriving stock (Generic)
|
||||
|
||||
deriving stock instance Backend b => Eq (TableObjectType b)
|
||||
|
||||
deriving stock instance Backend b => Show (TableObjectType b)
|
||||
|
||||
instance Backend b => NFData (TableObjectType b)
|
||||
|
||||
instance Backend b => ToJSON (TableObjectType b) where
|
||||
toJSON = genericToJSON hasuraJSON
|
||||
|
||||
instance Backend b => FromJSON (TableObjectType b) where
|
||||
parseJSON = genericParseJSON hasuraJSON
|
||||
|
||||
data TableObjectFieldDefinition (b :: BackendType) = TableObjectFieldDefinition
|
||||
{ _tofdColumn :: Column b,
|
||||
_tofdName :: G.Name,
|
||||
_tofdDescription :: Maybe G.Description,
|
||||
_tofdGType :: GraphQLType,
|
||||
_tofdFieldType :: TableObjectFieldType b
|
||||
}
|
||||
deriving stock (Generic)
|
||||
|
||||
deriving stock instance Backend b => Eq (TableObjectFieldDefinition b)
|
||||
|
||||
deriving stock instance Backend b => Show (TableObjectFieldDefinition b)
|
||||
|
||||
instance Backend b => NFData (TableObjectFieldDefinition b)
|
||||
|
||||
instance Backend b => ToJSON (TableObjectFieldDefinition b) where
|
||||
toJSON = genericToJSON hasuraJSON
|
||||
|
||||
instance Backend b => FromJSON (TableObjectFieldDefinition b) where
|
||||
parseJSON = genericParseJSON hasuraJSON
|
||||
|
||||
data TableObjectFieldType (b :: BackendType)
|
||||
= TOFTScalar G.Name (ScalarType b)
|
||||
| TOFTObject G.Name
|
||||
deriving stock (Generic)
|
||||
|
||||
deriving stock instance Backend b => Eq (TableObjectFieldType b)
|
||||
|
||||
deriving stock instance Backend b => Show (TableObjectFieldType b)
|
||||
|
||||
instance Backend b => NFData (TableObjectFieldType b)
|
||||
|
||||
instance Backend b => ToJSON (TableObjectFieldType b) where
|
||||
toJSON = genericToJSON hasuraJSON
|
||||
|
||||
instance Backend b => FromJSON (TableObjectFieldType b) where
|
||||
parseJSON = genericParseJSON hasuraJSON
|
||||
|
||||
-- | The @field@ and @primaryKeyColumn@ type parameters vary as the schema cache is built and more
|
||||
-- information is accumulated. See also 'TableCoreInfo'.
|
||||
data TableCoreInfoG (b :: BackendType) field primaryKeyColumn = TableCoreInfo
|
||||
@ -927,7 +1032,8 @@ data TableCoreInfoG (b :: BackendType) field primaryKeyColumn = TableCoreInfo
|
||||
_tciEnumValues :: Maybe EnumValues,
|
||||
_tciCustomConfig :: TableConfig b,
|
||||
_tciExtraTableMetadata :: ExtraTableMetadata b,
|
||||
_tciApolloFederationConfig :: Maybe ApolloFederationConfig
|
||||
_tciApolloFederationConfig :: Maybe ApolloFederationConfig,
|
||||
_tciCustomObjectTypes :: HashMap G.Name (TableObjectType b)
|
||||
}
|
||||
deriving (Generic)
|
||||
|
||||
@ -1044,7 +1150,8 @@ data DBTableMetadata (b :: BackendType) = DBTableMetadata
|
||||
_ptmiForeignKeys :: HashSet (ForeignKeyMetadata b),
|
||||
_ptmiViewInfo :: Maybe ViewInfo,
|
||||
_ptmiDescription :: Maybe Postgres.PGDescription,
|
||||
_ptmiExtraTableMetadata :: ExtraTableMetadata b
|
||||
_ptmiExtraTableMetadata :: ExtraTableMetadata b,
|
||||
_ptmiCustomObjectTypes :: Maybe (HashMap G.Name (TableObjectType b))
|
||||
}
|
||||
deriving (Generic)
|
||||
|
||||
@ -1099,6 +1206,7 @@ askColInfo m c msg = do
|
||||
askFieldInfo m (fromCol @backend c)
|
||||
case fieldInfo of
|
||||
(FIColumn colInfo) -> pure colInfo
|
||||
(FINestedObject _) -> throwErr "nested object"
|
||||
(FIRelationship _) -> throwErr "relationship"
|
||||
(FIComputedField _) -> throwErr "computed field"
|
||||
(FIRemoteRelationship _) -> throwErr "remote relationship"
|
||||
@ -1124,6 +1232,7 @@ askComputedFieldInfo fields computedField = do
|
||||
fromComputedField computedField
|
||||
case fieldInfo of
|
||||
(FIColumn _) -> throwErr "column"
|
||||
(FINestedObject _) -> throwErr "nested object"
|
||||
(FIRelationship _) -> throwErr "relationship"
|
||||
(FIRemoteRelationship _) -> throwErr "remote relationship"
|
||||
(FIComputedField cci) -> pure cci
|
||||
|
@ -16,9 +16,11 @@ import Test.Hspec
|
||||
spec :: Spec
|
||||
spec = do
|
||||
describe "SchemaResponse" $ do
|
||||
testToFromJSONToSchema (SchemaResponse []) [aesonQQ|{"tables": []}|]
|
||||
testToFromJSONToSchema (SchemaResponse [] Nothing) [aesonQQ|{"tables": []}|]
|
||||
jsonOpenApiProperties genSchemaResponse
|
||||
|
||||
genSchemaResponse :: (MonadGen m, GenBase m ~ Identity) => m SchemaResponse
|
||||
genSchemaResponse =
|
||||
SchemaResponse <$> Gen.list defaultRange genTableInfo
|
||||
SchemaResponse <$> Gen.list defaultRange genTableInfo <*> pure Nothing
|
||||
|
||||
-- TODO: fix generator to add GraphQLTypes
|
||||
|
@ -144,7 +144,8 @@ buildTableInfo TableInfoBuilder {..} = tableInfo
|
||||
_tciEnumValues = Nothing,
|
||||
_tciCustomConfig = tableConfig,
|
||||
_tciExtraTableMetadata = (),
|
||||
_tciApolloFederationConfig = Nothing
|
||||
_tciApolloFederationConfig = Nothing,
|
||||
_tciCustomObjectTypes = mempty
|
||||
}
|
||||
|
||||
pk :: Maybe (PrimaryKey PG (ColumnInfo PG))
|
||||
|
Loading…
Reference in New Issue
Block a user