From 87bcdb97c77b7a0a0f1f85afc2ff37aa5a94b0cc Mon Sep 17 00:00:00 2001 From: David Overton Date: Fri, 28 Oct 2022 12:12:54 +1100 Subject: [PATCH] Use JSON instead of GraphQL for comparison operators PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6563 GitOrigin-RevId: 0819414df199292e0146ce3009295ce3e49f2439 --- dc-agents/README.md | 30 +++-- dc-agents/dc-api-types/package.json | 2 +- dc-agents/dc-api-types/src/agent.openapi.json | 24 ++-- dc-agents/dc-api-types/src/index.ts | 3 +- .../dc-api-types/src/models/Capabilities.ts | 2 - .../src/models/ComparisonOperators.ts | 13 +++ .../dc-api-types/src/models/GraphQLName.ts | 8 -- .../src/models/GraphQLTypeDefinitions.ts | 8 -- .../src/models/ScalarTypeCapabilities.ts | 6 +- dc-agents/package-lock.json | 10 +- dc-agents/reference/package-lock.json | 4 +- dc-agents/reference/package.json | 2 +- dc-agents/reference/src/capabilities.ts | 15 +-- dc-agents/sqlite/package-lock.json | 4 +- dc-agents/sqlite/package.json | 2 +- dc-agents/sqlite/src/capabilities.ts | 1 - .../DataConnector/API/V0/Capabilities.hs | 108 +++++------------- .../Backend/DataConnector/Mock/Server.hs | 3 +- .../src/Harness/Test/BackendType.hs | 12 +- .../Backends/DataConnector/Adapter/Backend.hs | 4 +- .../Backends/DataConnector/Adapter/Schema.hs | 24 ++-- .../DataConnector/API/V0/CapabilitiesSpec.hs | 98 ++++------------ 22 files changed, 121 insertions(+), 262 deletions(-) create mode 100644 dc-agents/dc-api-types/src/models/ComparisonOperators.ts delete mode 100644 dc-agents/dc-api-types/src/models/GraphQLName.ts delete mode 100644 dc-agents/dc-api-types/src/models/GraphQLTypeDefinitions.ts diff --git a/dc-agents/README.md b/dc-agents/README.md index d095796b024..4f720efb87c 100644 --- a/dc-agents/README.md +++ b/dc-agents/README.md @@ -133,9 +133,8 @@ The `GET /capabilities` endpoint is used by `graphql-engine` to discover the cap "column_nullability": "nullable_and_non_nullable" }, "relationships": {}, - "graphql_schema": "scalar DateTime\n\ninput DateTimeComparisons {\n in_year: Number\n}", "scalar_types": { - "DateTime": {"comparisonType": "DateTimeComparisons"} + "DateTime": {"comparison_operators": {"DateTime": {"in_year": "Number"}}} } }, "config_schemas": { @@ -166,7 +165,6 @@ The `capabilities` section describes the _capabilities_ of the service. This inc - `data_schema`: What sorts of features the agent supports when describing its data schema - `relationships`: whether or not the agent supports relationships - `scalar_types`: custom scalar types and the operations they support. See [Scalar types capabilities](#scalar-type-capabilities). -- `graphql_schema`: a GraphQL schema document containing type definitions referenced by the `scalar_types` capabilities. The `config_schema` property contains an [OpenAPI 3 Schema](https://swagger.io/specification/#schema-object) object that represents the schema of the configuration object. It can use references (`$ref`) to refer to other schemas defined in the `other_schemas` object by name. @@ -180,7 +178,7 @@ If the agent only supports table columns that are always nullable, then it shoul #### Scalar type capabilities The agent is expected to support a default set of scalar types (`Number`, `String`, `Bool`) and a default set of [comparison operators](#filters) on these types. -Agents may optionally declare support for their own custom scalar types and custom comparison operators on those types. +Agents may optionally declare support for their own custom scalar types, along with custom comparison operators and aggregate functions on those types. Hasura GraphQL Engine does not validate the JSON format for values of custom scalar types. It passes them through transparently to the agent when they are used as GraphQL input values and returns them transparently when they are produced by the agent. It is the agent's responsibility to validate the values provided as GraphQL inputs. @@ -188,33 +186,31 @@ It is the agent's responsibility to validate the values provided as GraphQL inpu Custom scalar types are declared by adding a property to the `scalar_types` section of the [capabilities](#capabilities-and-configuration-schema) and by adding scalar type declaration with the same name in the `graphql_schema` capabilities property. -Custom comparison types can be defined by adding a `comparisonType` property to the scalar type capabilities object. -The `comparisonType` property gives the name of a GraphQL input object type, which must be defined in the `graphql_schema` capabilities property. -The input object type will be spliced into the `where` argument for any columns of the scalar type in the GraphQL schema. +Custom comparison types can be defined by adding a `comparison_operators` property to the scalar type capabilities object. +The `comparison_operators` property is an object where each key specifies a comparison operator name. +The operator name must be a valid GraphQL name. +The value associated with each key should be a string specifying the argument type, which must be a valid scalar type. -Custom aggregate functions can be defined by adding a `aggregate_functions` property to the scalar type capabilities object. +Custom aggregate functions can be defined by adding an `aggregate_functions` property to the scalar type capabilities object. The `aggregate_functions` property must be an object mapping aggregate function names to their result types. +Aggregate function names must be must be valid GraphQL names. +Result types must be valid scalar types. Example: ```yaml capabilities: - graphql_schema: | - scalar DateTime - - input DateTimeComparisons { - in_year: Number - } scalar_types: DateTime: - comparisonType: DateTimeComparisons + comparison_operators: + in_year: Number aggregate_functions: max: 'DateTime' min: 'DateTime' ``` -This example declares a custom scalar type `DateTime`, with comparison operators defined by the GraphQL input object type `DateTimeComparisons`. -The input type `DateTimeComparisons` defines one comparison operator `in_year` which takes a `Number` argument +This example declares a custom scalar type `DateTime`. +The type supports a comparison operator `in_year`, which takes an argument of type `Number`. An example GraphQL query using the custom comparison operator might look like below: ```graphql diff --git a/dc-agents/dc-api-types/package.json b/dc-agents/dc-api-types/package.json index a17de71f24f..8dfb98efe88 100644 --- a/dc-agents/dc-api-types/package.json +++ b/dc-agents/dc-api-types/package.json @@ -1,6 +1,6 @@ { "name": "@hasura/dc-api-types", - "version": "0.13.0", + "version": "0.14.0", "description": "Hasura GraphQL Engine Data Connector Agent API types", "author": "Hasura (https://github.com/hasura/graphql-engine)", "license": "Apache-2.0", diff --git a/dc-agents/dc-api-types/src/agent.openapi.json b/dc-agents/dc-api-types/src/agent.openapi.json index 8b0c6485aff..ef1a9e33e6d 100644 --- a/dc-agents/dc-api-types/src/agent.openapi.json +++ b/dc-agents/dc-api-types/src/agent.openapi.json @@ -312,9 +312,6 @@ "explain": { "$ref": "#/components/schemas/ExplainCapabilities" }, - "graphql_schema": { - "$ref": "#/components/schemas/GraphQLTypeDefinitions" - }, "metrics": { "$ref": "#/components/schemas/MetricsCapabilities" }, @@ -367,10 +364,6 @@ "QueryCapabilities": {}, "MutationCapabilities": {}, "SubscriptionCapabilities": {}, - "GraphQLName": { - "description": "A valid GraphQL name", - "type": "string" - }, "ScalarType": { "additionalProperties": true, "anyOf": [ @@ -387,6 +380,13 @@ } ] }, + "ComparisonOperators": { + "additionalProperties": { + "$ref": "#/components/schemas/ScalarType" + }, + "description": "A map from comparison operator names to their argument types.\nOperator and argument type names must be valid GraphQL names.\nResult type names must be defined scalar types - either builtin or declared in ScalarTypesCapabilities.\n", + "type": "object" + }, "AggregateFunctions": { "additionalProperties": { "$ref": "#/components/schemas/ScalarType" @@ -395,13 +395,13 @@ "type": "object" }, "ScalarTypeCapabilities": { - "description": "Capabilities of a scalar type.\ncomparison_type: Name of the GraphQL input object to be used for comparison operations on the scalar type. The input object type must be defined in the `graphql_schema`.\naggregate_functions: The aggregate functions supported by the scalar type.\n", + "description": "Capabilities of a scalar type.\ncomparison_operators: The comparison operators supported by the scalar type.\naggregate_functions: The aggregate functions supported by the scalar type.\n", "properties": { "aggregate_functions": { "$ref": "#/components/schemas/AggregateFunctions" }, - "comparison_type": { - "$ref": "#/components/schemas/GraphQLName" + "comparison_operators": { + "$ref": "#/components/schemas/ComparisonOperators" } }, "type": "object" @@ -413,10 +413,6 @@ "description": "A map from scalar type names to their capabilities.\nKeys must be valid GraphQL names and must be defined as scalar types in the `graphql_schema`\n", "type": "object" }, - "GraphQLTypeDefinitions": { - "description": "A valid GraphQL schema document containing type definitions", - "type": "string" - }, "RelationshipCapabilities": {}, "SubqueryComparisonCapabilities": { "nullable": true, diff --git a/dc-agents/dc-api-types/src/index.ts b/dc-agents/dc-api-types/src/index.ts index a55f8a36d22..0bbe08b20a5 100644 --- a/dc-agents/dc-api-types/src/index.ts +++ b/dc-agents/dc-api-types/src/index.ts @@ -20,6 +20,7 @@ export type { ColumnInfo } from './models/ColumnInfo'; export type { ColumnNullability } from './models/ColumnNullability'; export type { ComparisonCapabilities } from './models/ComparisonCapabilities'; export type { ComparisonColumn } from './models/ComparisonColumn'; +export type { ComparisonOperators } from './models/ComparisonOperators'; export type { ComparisonValue } from './models/ComparisonValue'; export type { ConfigSchemaResponse } from './models/ConfigSchemaResponse'; export type { Constraint } from './models/Constraint'; @@ -32,8 +33,6 @@ export type { ExplainCapabilities } from './models/ExplainCapabilities'; export type { ExplainResponse } from './models/ExplainResponse'; export type { Expression } from './models/Expression'; export type { Field } from './models/Field'; -export type { GraphQLName } from './models/GraphQLName'; -export type { GraphQLTypeDefinitions } from './models/GraphQLTypeDefinitions'; export type { MetricsCapabilities } from './models/MetricsCapabilities'; export type { MutationCapabilities } from './models/MutationCapabilities'; export type { NotExpression } from './models/NotExpression'; diff --git a/dc-agents/dc-api-types/src/models/Capabilities.ts b/dc-agents/dc-api-types/src/models/Capabilities.ts index 7863496407a..91d5d0c9459 100644 --- a/dc-agents/dc-api-types/src/models/Capabilities.ts +++ b/dc-agents/dc-api-types/src/models/Capabilities.ts @@ -5,7 +5,6 @@ import type { ComparisonCapabilities } from './ComparisonCapabilities'; import type { DataSchemaCapabilities } from './DataSchemaCapabilities'; import type { ExplainCapabilities } from './ExplainCapabilities'; -import type { GraphQLTypeDefinitions } from './GraphQLTypeDefinitions'; import type { MetricsCapabilities } from './MetricsCapabilities'; import type { MutationCapabilities } from './MutationCapabilities'; import type { QueryCapabilities } from './QueryCapabilities'; @@ -18,7 +17,6 @@ export type Capabilities = { comparisons?: ComparisonCapabilities; data_schema?: DataSchemaCapabilities; explain?: ExplainCapabilities; - graphql_schema?: GraphQLTypeDefinitions; metrics?: MetricsCapabilities; mutations?: MutationCapabilities; queries?: QueryCapabilities; diff --git a/dc-agents/dc-api-types/src/models/ComparisonOperators.ts b/dc-agents/dc-api-types/src/models/ComparisonOperators.ts new file mode 100644 index 00000000000..f27b52a4fe5 --- /dev/null +++ b/dc-agents/dc-api-types/src/models/ComparisonOperators.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ScalarType } from './ScalarType'; + +/** + * A map from comparison operator names to their argument types. + * Operator and argument type names must be valid GraphQL names. + * Result type names must be defined scalar types - either builtin or declared in ScalarTypesCapabilities. + * + */ +export type ComparisonOperators = Record; diff --git a/dc-agents/dc-api-types/src/models/GraphQLName.ts b/dc-agents/dc-api-types/src/models/GraphQLName.ts deleted file mode 100644 index 8c0e128a623..00000000000 --- a/dc-agents/dc-api-types/src/models/GraphQLName.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * A valid GraphQL name - */ -export type GraphQLName = string; diff --git a/dc-agents/dc-api-types/src/models/GraphQLTypeDefinitions.ts b/dc-agents/dc-api-types/src/models/GraphQLTypeDefinitions.ts deleted file mode 100644 index 9dc6975f7cd..00000000000 --- a/dc-agents/dc-api-types/src/models/GraphQLTypeDefinitions.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * A valid GraphQL schema document containing type definitions - */ -export type GraphQLTypeDefinitions = string; diff --git a/dc-agents/dc-api-types/src/models/ScalarTypeCapabilities.ts b/dc-agents/dc-api-types/src/models/ScalarTypeCapabilities.ts index b993c318b57..154dbaf5254 100644 --- a/dc-agents/dc-api-types/src/models/ScalarTypeCapabilities.ts +++ b/dc-agents/dc-api-types/src/models/ScalarTypeCapabilities.ts @@ -3,16 +3,16 @@ /* eslint-disable */ import type { AggregateFunctions } from './AggregateFunctions'; -import type { GraphQLName } from './GraphQLName'; +import type { ComparisonOperators } from './ComparisonOperators'; /** * Capabilities of a scalar type. - * comparison_type: Name of the GraphQL input object to be used for comparison operations on the scalar type. The input object type must be defined in the `graphql_schema`. + * comparison_operators: The comparison operators supported by the scalar type. * aggregate_functions: The aggregate functions supported by the scalar type. * */ export type ScalarTypeCapabilities = { aggregate_functions?: AggregateFunctions; - comparison_type?: GraphQLName; + comparison_operators?: ComparisonOperators; }; diff --git a/dc-agents/package-lock.json b/dc-agents/package-lock.json index f1d22fabce7..a4d491bb3e6 100644 --- a/dc-agents/package-lock.json +++ b/dc-agents/package-lock.json @@ -24,7 +24,7 @@ }, "dc-api-types": { "name": "@hasura/dc-api-types", - "version": "0.13.0", + "version": "0.14.0", "license": "Apache-2.0", "devDependencies": { "@tsconfig/node16": "^1.0.3", @@ -631,7 +631,7 @@ "license": "Apache-2.0", "dependencies": { "@fastify/cors": "^7.0.0", - "@hasura/dc-api-types": "0.13.0", + "@hasura/dc-api-types": "0.14.0", "fastify": "^3.29.0", "mathjs": "^11.0.0", "pino-pretty": "^8.0.0", @@ -1389,7 +1389,7 @@ "license": "Apache-2.0", "dependencies": { "@fastify/cors": "^8.1.0", - "@hasura/dc-api-types": "0.13.0", + "@hasura/dc-api-types": "0.14.0", "fastify": "^4.4.0", "fastify-metrics": "^9.2.1", "nanoid": "^3.3.4", @@ -3122,7 +3122,7 @@ "version": "file:reference", "requires": { "@fastify/cors": "^7.0.0", - "@hasura/dc-api-types": "0.13.0", + "@hasura/dc-api-types": "0.14.0", "@tsconfig/node16": "^1.0.3", "@types/node": "^16.11.49", "@types/xml2js": "^0.4.11", @@ -3613,7 +3613,7 @@ "version": "file:sqlite", "requires": { "@fastify/cors": "^8.1.0", - "@hasura/dc-api-types": "0.13.0", + "@hasura/dc-api-types": "0.14.0", "@tsconfig/node16": "^1.0.3", "@types/node": "^16.11.49", "@types/sqlite3": "^3.1.8", diff --git a/dc-agents/reference/package-lock.json b/dc-agents/reference/package-lock.json index ba8fd58baa5..3caf6eead39 100644 --- a/dc-agents/reference/package-lock.json +++ b/dc-agents/reference/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@fastify/cors": "^7.0.0", - "@hasura/dc-api-types": "0.13.0", + "@hasura/dc-api-types": "0.14.0", "fastify": "^3.29.0", "mathjs": "^11.0.0", "pino-pretty": "^8.0.0", @@ -44,7 +44,7 @@ } }, "node_modules/@hasura/dc-api-types": { - "version": "0.13.0", + "version": "0.14.0", "license": "Apache-2.0", "devDependencies": { "@tsconfig/node16": "^1.0.3", diff --git a/dc-agents/reference/package.json b/dc-agents/reference/package.json index 003982cc605..63f39612610 100644 --- a/dc-agents/reference/package.json +++ b/dc-agents/reference/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@fastify/cors": "^7.0.0", - "@hasura/dc-api-types": "0.13.0", + "@hasura/dc-api-types": "0.14.0", "fastify": "^3.29.0", "mathjs": "^11.0.0", "pino-pretty": "^8.0.0", diff --git a/dc-agents/reference/src/capabilities.ts b/dc-agents/reference/src/capabilities.ts index 4bb6120bc47..e3bb34e804e 100644 --- a/dc-agents/reference/src/capabilities.ts +++ b/dc-agents/reference/src/capabilities.ts @@ -1,17 +1,11 @@ import { configSchema } from "./config" import { Capabilities, CapabilitiesResponse, ScalarTypeCapabilities, ScalarTypesCapabilities } from "@hasura/dc-api-types" -const schemaDoc: string = - `scalar DateTime - -input DateTimeComparisons { - same_day_as: DateTime - in_year: Int -} -` - const dateTimeCapabilities: ScalarTypeCapabilities = { - comparison_type: 'DateTimeComparisons', + comparison_operators: { + same_day_as: 'DateTime', + in_year: 'Int' + }, aggregate_functions: { max: 'DateTime', min: 'DateTime' @@ -43,7 +37,6 @@ const capabilities: Capabilities = { supports_relations: true } }, - graphql_schema: schemaDoc, scalar_types: scalarTypes } diff --git a/dc-agents/sqlite/package-lock.json b/dc-agents/sqlite/package-lock.json index 5715a8a5b34..30c51b5ea3e 100644 --- a/dc-agents/sqlite/package-lock.json +++ b/dc-agents/sqlite/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@fastify/cors": "^8.1.0", - "@hasura/dc-api-types": "0.13.0", + "@hasura/dc-api-types": "0.14.0", "fastify": "^4.4.0", "fastify-metrics": "^9.2.1", "nanoid": "^3.3.4", @@ -54,7 +54,7 @@ "license": "MIT" }, "node_modules/@hasura/dc-api-types": { - "version": "0.13.0", + "version": "0.14.0", "license": "Apache-2.0", "devDependencies": { "@tsconfig/node16": "^1.0.3", diff --git a/dc-agents/sqlite/package.json b/dc-agents/sqlite/package.json index 26eea7162d8..7e7d49a2f6f 100644 --- a/dc-agents/sqlite/package.json +++ b/dc-agents/sqlite/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@fastify/cors": "^8.1.0", - "@hasura/dc-api-types": "0.13.0", + "@hasura/dc-api-types": "0.14.0", "fastify-metrics": "^9.2.1", "fastify": "^4.4.0", "nanoid": "^3.3.4", diff --git a/dc-agents/sqlite/src/capabilities.ts b/dc-agents/sqlite/src/capabilities.ts index 0e5601ec782..2c00bfd8bbd 100644 --- a/dc-agents/sqlite/src/capabilities.ts +++ b/dc-agents/sqlite/src/capabilities.ts @@ -10,7 +10,6 @@ export const capabilitiesResponse: CapabilitiesResponse = { supports_foreign_keys: true, column_nullability: "nullable_and_non_nullable", }, - graphql_schema: "scalar DateTime", scalar_types: { DateTime: {} }, diff --git a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Capabilities.hs b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Capabilities.hs index 54daf221ce9..bec988b5b35 100644 --- a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Capabilities.hs +++ b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Capabilities.hs @@ -13,10 +13,10 @@ module Hasura.Backends.DataConnector.API.V0.Capabilities QueryCapabilities (..), MutationCapabilities (..), SubscriptionCapabilities (..), + ComparisonOperators (..), AggregateFunctions (..), ScalarTypeCapabilities (..), ScalarTypesCapabilities (..), - GraphQLTypeDefinitions, RelationshipCapabilities (..), ComparisonCapabilities (..), SubqueryComparisonCapabilities (..), @@ -24,8 +24,6 @@ module Hasura.Backends.DataConnector.API.V0.Capabilities ExplainCapabilities (..), RawCapabilities (..), CapabilitiesResponse (..), - lookupComparisonInputObjectDefinition, - mkGraphQLTypeDefinitions, ) where @@ -70,8 +68,7 @@ data Capabilities = Capabilities _cQueries :: Maybe QueryCapabilities, _cMutations :: Maybe MutationCapabilities, _cSubscriptions :: Maybe SubscriptionCapabilities, - _cScalarTypes :: Maybe ScalarTypesCapabilities, - _cGraphQLTypeDefinitions :: Maybe GraphQLTypeDefinitions, + _cScalarTypes :: ScalarTypesCapabilities, _cRelationships :: Maybe RelationshipCapabilities, _cComparisons :: Maybe ComparisonCapabilities, _cMetrics :: Maybe MetricsCapabilities, @@ -83,7 +80,7 @@ data Capabilities = Capabilities deriving (FromJSON, ToJSON, ToSchema) via Autodocodec Capabilities defaultCapabilities :: Capabilities -defaultCapabilities = Capabilities defaultDataSchemaCapabilities Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing +defaultCapabilities = Capabilities defaultDataSchemaCapabilities Nothing Nothing Nothing mempty Nothing Nothing Nothing Nothing Nothing instance HasCodec Capabilities where codec = @@ -93,8 +90,7 @@ instance HasCodec Capabilities where <*> optionalField "queries" "The agent's query capabilities" .= _cQueries <*> optionalField "mutations" "The agent's mutation capabilities" .= _cMutations <*> optionalField "subscriptions" "The agent's subscription capabilities" .= _cSubscriptions - <*> optionalField "scalar_types" "The agent's scalar types and their capabilities" .= _cScalarTypes - <*> optionalField "graphql_schema" "A GraphQL Schema Document describing the agent's scalar types and input object types for comparison operators" .= _cGraphQLTypeDefinitions + <*> optionalFieldWithOmittedDefault "scalar_types" mempty "The agent's scalar types and their capabilities" .= _cScalarTypes <*> optionalField "relationships" "The agent's relationship capabilities" .= _cRelationships <*> optionalField "comparisons" "The agent's comparison capabilities" .= _cComparisons <*> optionalField "metrics" "The agent's metrics capabilities" .= _cMetrics @@ -170,11 +166,29 @@ data RelationshipCapabilities = RelationshipCapabilities {} instance HasCodec RelationshipCapabilities where codec = object "RelationshipCapabilities" $ pure RelationshipCapabilities +newtype ComparisonOperators = ComparisonOperators + { unComparisonOperators :: HashMap GQL.Syntax.Name ScalarType + } + deriving stock (Eq, Ord, Show, Generic) + deriving anyclass (NFData, Hashable) + deriving newtype (Semigroup, Monoid) + deriving (FromJSON, ToJSON, ToSchema) via Autodocodec ComparisonOperators + +instance HasCodec ComparisonOperators where + codec = + named "ComparisonOperators" $ + dimapCodec ComparisonOperators unComparisonOperators (hashMapCodec codec) + [ "A map from comparison operator names to their argument types.", + "Operator and argument type names must be valid GraphQL names.", + "Result type names must be defined scalar types - either builtin or declared in ScalarTypesCapabilities." + ] + newtype AggregateFunctions = AggregateFunctions { unAggregateFunctions :: HashMap GQL.Syntax.Name ScalarType } deriving stock (Eq, Ord, Show, Generic) deriving anyclass (NFData, Hashable) + deriving newtype (Semigroup, Monoid) deriving (FromJSON, ToJSON, ToSchema) via Autodocodec AggregateFunctions instance HasCodec AggregateFunctions where @@ -187,8 +201,8 @@ instance HasCodec AggregateFunctions where ] data ScalarTypeCapabilities = ScalarTypeCapabilities - { _stcComparisonInputObject :: Maybe GQL.Syntax.Name, - _stcAggregateFunctions :: Maybe AggregateFunctions + { _stcComparisonOperators :: ComparisonOperators, + _stcAggregateFunctions :: AggregateFunctions } deriving stock (Eq, Ord, Show, Generic) deriving anyclass (NFData, Hashable) @@ -199,11 +213,11 @@ instance HasCodec ScalarTypeCapabilities where object "ScalarTypeCapabilities" ( ScalarTypeCapabilities - <$> optionalFieldWith' "comparison_type" nameCodec .= _stcComparisonInputObject - <*> optionalField' "aggregate_functions" .= _stcAggregateFunctions + <$> optionalFieldWithOmittedDefault' "comparison_operators" mempty .= _stcComparisonOperators + <*> optionalFieldWithOmittedDefault' "aggregate_functions" mempty .= _stcAggregateFunctions ) [ "Capabilities of a scalar type.", - "comparison_type: Name of the GraphQL input object to be used for comparison operations on the scalar type. The input object type must be defined in the `graphql_schema`.", + "comparison_operators: The comparison operators supported by the scalar type.", "aggregate_functions: The aggregate functions supported by the scalar type." ] @@ -223,63 +237,6 @@ instance HasCodec ScalarTypesCapabilities where "Keys must be valid GraphQL names and must be defined as scalar types in the `graphql_schema`" ] -type TypeDefinition = GQL.Syntax.TypeDefinition () GQL.Syntax.InputValueDefinition - -mkGraphQLTypeDefinitions :: NonEmpty TypeDefinition -> GraphQLTypeDefinitions -mkGraphQLTypeDefinitions = - GraphQLTypeDefinitions - . InsOrdHashMap.fromList - . toList - . fmap (\td -> (getName td, td)) - where - getName :: TypeDefinition -> GQL.Syntax.Name - getName = \case - GQL.Syntax.TypeDefinitionScalar GQL.Syntax.ScalarTypeDefinition {..} -> _stdName - GQL.Syntax.TypeDefinitionObject GQL.Syntax.ObjectTypeDefinition {..} -> _otdName - GQL.Syntax.TypeDefinitionInterface GQL.Syntax.InterfaceTypeDefinition {..} -> _itdName - GQL.Syntax.TypeDefinitionUnion GQL.Syntax.UnionTypeDefinition {..} -> _utdName - GQL.Syntax.TypeDefinitionEnum GQL.Syntax.EnumTypeDefinition {..} -> _etdName - GQL.Syntax.TypeDefinitionInputObject GQL.Syntax.InputObjectTypeDefinition {..} -> _iotdName - -newtype GraphQLTypeDefinitions = GraphQLTypeDefinitions - { _gtdTypeDefinitions :: InsOrdHashMap GQL.Syntax.Name TypeDefinition - } - deriving stock (Eq, Show, Generic) - deriving anyclass (NFData, Hashable) - deriving (FromJSON, ToJSON, ToSchema) via Autodocodec GraphQLTypeDefinitions - -instance HasCodec GraphQLTypeDefinitions where - codec = - bimapCodec parseTypeDefinitions printTypeDefinitions (StringCodec (Just "GraphQLTypeDefinitions")) - "A valid GraphQL schema document containing type definitions" - where - -- Note: any `SchemaDefinition`s in the parsed `SchemaDocument` will be ignored. - -- We don't need them, we're only interested in the `TypeDefinition`s defined in the document. - getTypeDefinition :: GQL.Syntax.TypeSystemDefinition -> Maybe TypeDefinition - getTypeDefinition = \case - GQL.Syntax.TypeSystemDefinitionSchema _ -> Nothing - GQL.Syntax.TypeSystemDefinitionType td -> Just td - - fromSchemaDocument :: GQL.Syntax.SchemaDocument -> Either String GraphQLTypeDefinitions - fromSchemaDocument (GQL.Syntax.SchemaDocument typeSystemDefinitions) = - case nonEmpty $ mapMaybe getTypeDefinition typeSystemDefinitions of - Nothing -> Left "No type definitions found in schema document" - Just typeDefinitions -> Right $ mkGraphQLTypeDefinitions typeDefinitions - - parseTypeDefinitions :: Text -> Either String GraphQLTypeDefinitions - parseTypeDefinitions = - fromSchemaDocument <=< first Text.unpack . GQL.Parser.parseSchemaDocument - - printTypeDefinitions :: GraphQLTypeDefinitions -> Text - printTypeDefinitions = - toStrict - . Builder.toLazyText - . GQL.Printer.schemaDocument - . GQL.Syntax.SchemaDocument - . fmap GQL.Syntax.TypeSystemDefinitionType - . toList - . _gtdTypeDefinitions - data ComparisonCapabilities = ComparisonCapabilities {_ccSubqueryComparisonCapabilities :: Maybe SubqueryComparisonCapabilities} deriving stock (Eq, Ord, Show, Generic, Data) @@ -365,14 +322,3 @@ instance ToSchema CapabilitiesResponse where } pure $ NamedSchema (Just "CapabilitiesResponse") schema - -lookupComparisonInputObjectDefinition :: Capabilities -> ScalarType -> Maybe (GQL.Syntax.InputObjectTypeDefinition GQL.Syntax.InputValueDefinition) -lookupComparisonInputObjectDefinition Capabilities {..} typeName = do - scalarTypesMap <- _cScalarTypes - ScalarTypeCapabilities {..} <- HashMap.lookup typeName $ unScalarTypesCapabilities scalarTypesMap - comparisonTypeName <- _stcComparisonInputObject - typeDefinitions <- _cGraphQLTypeDefinitions - typeDefinition <- InsOrdHashMap.lookup comparisonTypeName $ _gtdTypeDefinitions typeDefinitions - case typeDefinition of - GQL.Syntax.TypeDefinitionInputObject inputObjectTypeDefinition -> Just inputObjectTypeDefinition - _ -> Nothing diff --git a/server/lib/test-harness/src/Harness/Backend/DataConnector/Mock/Server.hs b/server/lib/test-harness/src/Harness/Backend/DataConnector/Mock/Server.hs index c26a55514db..e6fce832656 100644 --- a/server/lib/test-harness/src/Harness/Backend/DataConnector/Mock/Server.hs +++ b/server/lib/test-harness/src/Harness/Backend/DataConnector/Mock/Server.hs @@ -42,8 +42,7 @@ capabilities = API._cQueries = Just API.QueryCapabilities, API._cMutations = Nothing, API._cSubscriptions = Nothing, - API._cScalarTypes = Nothing, - API._cGraphQLTypeDefinitions = Nothing, + API._cScalarTypes = mempty, API._cRelationships = Just API.RelationshipCapabilities {}, API._cComparisons = Just diff --git a/server/lib/test-harness/src/Harness/Test/BackendType.hs b/server/lib/test-harness/src/Harness/Test/BackendType.hs index e948beaffa7..cec98df7258 100644 --- a/server/lib/test-harness/src/Harness/Test/BackendType.hs +++ b/server/lib/test-harness/src/Harness/Test/BackendType.hs @@ -56,8 +56,6 @@ defaultBackendCapabilities = \case supports_foreign_keys: true scalar_types: DateTime: {} - graphql_schema: |- - scalar DateTime queries: {} relationships: {} comparisons: @@ -74,19 +72,15 @@ defaultBackendCapabilities = \case supports_primary_keys: true supports_foreign_keys: true queries: {} - graphql_schema: |- - scalar DateTime - - input DateTimeComparisons {in_year: Int - same_day_as: DateTime - } relationships: {} comparisons: subquery: supports_relations: true scalar_types: DateTime: - comparison_type: DateTimeComparisons + comparison_operators: + same_day_as: DateTime + in_year: Int aggregate_functions: max: DateTime min: DateTime diff --git a/server/src-lib/Hasura/Backends/DataConnector/Adapter/Backend.hs b/server/src-lib/Hasura/Backends/DataConnector/Adapter/Backend.hs index 893e62832ff..dbd6f49f707 100644 --- a/server/src-lib/Hasura/Backends/DataConnector/Adapter/Backend.hs +++ b/server/src-lib/Hasura/Backends/DataConnector/Adapter/Backend.hs @@ -92,10 +92,10 @@ instance Backend 'DataConnector where getCustomAggregateOperators Adapter.SourceConfig {..} = HashMap.foldrWithKey insertOps mempty scalarTypesCapabilities where - scalarTypesCapabilities = maybe mempty API.unScalarTypesCapabilities $ API._cScalarTypes _scCapabilities + scalarTypesCapabilities = API.unScalarTypesCapabilities $ API._cScalarTypes _scCapabilities insertOps typeName API.ScalarTypeCapabilities {..} m = HashMap.foldrWithKey insertOp m $ - maybe mempty API.unAggregateFunctions _stcAggregateFunctions + API.unAggregateFunctions _stcAggregateFunctions where insertOp funtionName resultTypeName = HashMap.insertWith HashMap.union funtionName $ diff --git a/server/src-lib/Hasura/Backends/DataConnector/Adapter/Schema.hs b/server/src-lib/Hasura/Backends/DataConnector/Adapter/Schema.hs index bc61c935d95..8902603173a 100644 --- a/server/src-lib/Hasura/Backends/DataConnector/Adapter/Schema.hs +++ b/server/src-lib/Hasura/Backends/DataConnector/Adapter/Schema.hs @@ -12,7 +12,7 @@ import Data.HashMap.Strict qualified as Map import Data.List.NonEmpty qualified as NE import Data.Text.Casing (GQLNameIdentifier, fromCustomName) import Data.Text.Extended ((<<>)) -import Hasura.Backends.DataConnector.API.V0.Capabilities (lookupComparisonInputObjectDefinition) +import Hasura.Backends.DataConnector.API qualified as API import Hasura.Backends.DataConnector.Adapter.Backend (CustomBooleanOperator (..), columnTypeToScalarType) import Hasura.Backends.DataConnector.Adapter.Types qualified as DC import Hasura.Base.Error @@ -204,28 +204,28 @@ comparisonExps' sourceInfo columnType = P.memoizeOn 'comparisonExps' (dataConnec GS.C.SchemaT r m [P.InputFieldsParser n (Maybe (CustomBooleanOperator (IR.UnpreparedValue 'DataConnector)))] mkCustomOperators tCase collapseIfNull typeName = do let capabilities = sourceInfo ^. RQL.siConfiguration . DC.scCapabilities - case lookupComparisonInputObjectDefinition capabilities (Witch.from $ DC.fromGQLType typeName) of + case Map.lookup (Witch.from $ DC.fromGQLType typeName) (API.unScalarTypesCapabilities $ API._cScalarTypes capabilities) of Nothing -> pure [] - Just GQL.InputObjectTypeDefinition {..} -> do - traverse (mkCustomOperator tCase collapseIfNull) _iotdValueDefinitions + Just API.ScalarTypeCapabilities {..} -> do + traverse (mkCustomOperator tCase collapseIfNull) $ Map.toList $ fmap Witch.from $ API.unComparisonOperators $ _stcComparisonOperators mkCustomOperator :: NamingCase -> Options.DangerouslyCollapseBooleans -> - GQL.InputValueDefinition -> + (GQL.Name, DC.ScalarType) -> GS.C.SchemaT r m (P.InputFieldsParser n (Maybe (CustomBooleanOperator (IR.UnpreparedValue 'DataConnector)))) - mkCustomOperator tCase collapseIfNull GQL.InputValueDefinition {..} = do - argParser <- mkArgParser _ivdType + mkCustomOperator tCase collapseIfNull (operatorName, argType) = do + argParser <- mkArgParser argType pure $ - GS.BE.mkBoolOperator tCase collapseIfNull (fromCustomName _ivdName) _ivdDescription $ - CustomBooleanOperator (GQL.unName _ivdName) . Just . Right <$> argParser + GS.BE.mkBoolOperator tCase collapseIfNull (fromCustomName operatorName) Nothing $ + CustomBooleanOperator (GQL.unName operatorName) . Just . Right <$> argParser - mkArgParser :: GQL.GType -> GS.C.SchemaT r m (P.Parser 'P.Both n (IR.UnpreparedValue 'DataConnector)) + mkArgParser :: DC.ScalarType -> GS.C.SchemaT r m (P.Parser 'P.Both n (IR.UnpreparedValue 'DataConnector)) mkArgParser argType = fmap IR.mkParameter <$> columnParser' - (RQL.ColumnScalar $ DC.fromGQLType $ GQL.getBaseType argType) - (GQL.Nullability $ GQL.isNotNull argType) + (RQL.ColumnScalar argType) + (GQL.Nullability True) tableArgs' :: forall r m n. diff --git a/server/src-test/Hasura/Backends/DataConnector/API/V0/CapabilitiesSpec.hs b/server/src-test/Hasura/Backends/DataConnector/API/V0/CapabilitiesSpec.hs index b9bee14fa1e..e1b52b57ce8 100644 --- a/server/src-test/Hasura/Backends/DataConnector/API/V0/CapabilitiesSpec.hs +++ b/server/src-test/Hasura/Backends/DataConnector/API/V0/CapabilitiesSpec.hs @@ -3,10 +3,8 @@ module Hasura.Backends.DataConnector.API.V0.CapabilitiesSpec (spec) where -import Data.Aeson (Value (..)) import Data.Aeson.QQ.Simple (aesonQQ) import Data.HashMap.Strict qualified as HashMap -import Data.Text.RawString (raw) import Hasura.Backends.DataConnector.API.V0.Capabilities import Hasura.Backends.DataConnector.API.V0.ConfigSchema import Hasura.Backends.DataConnector.API.V0.Scalar (ScalarType (..)) @@ -15,8 +13,6 @@ import Hasura.Generator.Common import Hasura.Prelude import Hedgehog import Hedgehog.Gen qualified as Gen -import Language.GraphQL.Draft.Generator (genTypeDefinition) -import Language.GraphQL.Draft.Syntax qualified as G import Language.GraphQL.Draft.Syntax.QQ qualified as G import Test.Aeson.Utils import Test.Hspec @@ -31,36 +27,21 @@ spec = do (CapabilitiesResponse (defaultCapabilities {_cRelationships = Just RelationshipCapabilities {}}) emptyConfigSchemaResponse) [aesonQQ|{"capabilities": {"relationships": {}}, "config_schemas": {"config_schema": {}, "other_schemas": {}}}|] describe "ScalarTypesCapabilities" $ do - testToFromJSONToSchema (ScalarTypesCapabilities (HashMap.singleton StringTy (ScalarTypeCapabilities Nothing Nothing))) [aesonQQ|{"string": {}}|] + testToFromJSONToSchema (ScalarTypesCapabilities (HashMap.singleton StringTy (ScalarTypeCapabilities mempty mempty))) [aesonQQ|{"string": {}}|] jsonOpenApiProperties genScalarTypesCapabilities describe "ScalarTypeCapabilities" $ do - testToFromJSONToSchema (ScalarTypeCapabilities (Just [G.name|DateTimeComparisons|]) Nothing) [aesonQQ|{"comparison_type": "DateTimeComparisons"}|] - describe "GraphQLTypeDefinitions" $ do - testToFromJSONToSchema sampleGraphQLTypeDefinitions sampleGraphQLTypeDefinitionsJSON - -sampleGraphQLTypeDefinitions :: GraphQLTypeDefinitions -sampleGraphQLTypeDefinitions = - mkGraphQLTypeDefinitions - [ G.TypeDefinitionScalar $ G.ScalarTypeDefinition Nothing [G.name|DateTime|] [], - G.TypeDefinitionInputObject $ - G.InputObjectTypeDefinition - Nothing - [G.name|DateTimeComparisons|] - [] - [ G.InputValueDefinition Nothing [G.name|after|] (G.TypeNamed (G.Nullability True) [G.name|DateTime|]) Nothing [], - G.InputValueDefinition Nothing [G.name|before|] (G.TypeNamed (G.Nullability True) [G.name|DateTime|]) Nothing [], - G.InputValueDefinition Nothing [G.name|in_year|] (G.TypeNamed (G.Nullability True) [G.name|Int|]) Nothing [] - ] - ] - -sampleGraphQLTypeDefinitionsJSON :: Value -sampleGraphQLTypeDefinitionsJSON = - [raw|scalar DateTime - -input DateTimeComparisons {after: DateTime - before: DateTime - in_year: Int -}|] + let comparisonOperators = ComparisonOperators $ HashMap.fromList [([G.name|same_day_as|], CustomTy "DateTime")] + let aggregateFunctions = AggregateFunctions $ HashMap.fromList [([G.name|max|], CustomTy "DateTime")] + let json = + [aesonQQ|{ + "comparison_operators": { + "same_day_as": "DateTime" + }, + "aggregate_functions": { + "max": "DateTime" + } + }|] + testToFromJSONToSchema (ScalarTypeCapabilities comparisonOperators aggregateFunctions) json genDataSchemaCapabilities :: MonadGen m => m DataSchemaCapabilities genDataSchemaCapabilities = @@ -82,6 +63,10 @@ genMutationCapabilities = pure MutationCapabilities {} genSubscriptionCapabilities :: MonadGen m => m SubscriptionCapabilities genSubscriptionCapabilities = pure SubscriptionCapabilities {} +genComparisonOperators :: MonadGen m => m ComparisonOperators +genComparisonOperators = + ComparisonOperators <$> genHashMap (genGName defaultRange) genScalarType defaultRange + genAggregateFunctions :: MonadGen m => m AggregateFunctions genAggregateFunctions = AggregateFunctions <$> genHashMap (genGName defaultRange) genScalarType defaultRange @@ -89,55 +74,13 @@ genAggregateFunctions = genScalarTypeCapabilities :: MonadGen m => m ScalarTypeCapabilities genScalarTypeCapabilities = ScalarTypeCapabilities - <$> Gen.maybe (genGName defaultRange) - <*> Gen.maybe genAggregateFunctions + <$> genComparisonOperators + <*> genAggregateFunctions genScalarTypesCapabilities :: MonadGen m => m ScalarTypesCapabilities genScalarTypesCapabilities = ScalarTypesCapabilities <$> genHashMap genScalarType genScalarTypeCapabilities defaultRange --- | 'genTypeDefinition' generates invalid type definitions so we need to filter them out. --- The printers also sort various lists upon printing, so we need to pre-sort them for round-tripping to work. --- The printer for 'ObjectTypeDefinition' prints directives in the wrong place so we only allow --- definitions with no directives. --- TODO: fix this in `graphql-parser-hs`. -isValidTypeDefinition :: Ord inputType => G.TypeDefinition possibleTypes inputType -> Maybe (G.TypeDefinition possibleTypes inputType) -isValidTypeDefinition = \case - t@(G.TypeDefinitionScalar G.ScalarTypeDefinition {}) -> Just t - G.TypeDefinitionObject G.ObjectTypeDefinition {..} -> do - guard $ not $ null _otdFieldsDefinition - Just $ - G.TypeDefinitionObject - G.ObjectTypeDefinition - { _otdFieldsDefinition = sort _otdFieldsDefinition, - _otdDirectives = [], - .. - } - G.TypeDefinitionInterface G.InterfaceTypeDefinition {..} -> do - guard $ not $ null _itdFieldsDefinition - Just $ - G.TypeDefinitionInterface - G.InterfaceTypeDefinition {_itdFieldsDefinition = sort _itdFieldsDefinition, ..} - G.TypeDefinitionUnion G.UnionTypeDefinition {..} -> do - guard $ not $ null _utdMemberTypes - Just $ - G.TypeDefinitionUnion - G.UnionTypeDefinition {_utdMemberTypes = sort _utdMemberTypes, ..} - G.TypeDefinitionEnum G.EnumTypeDefinition {..} -> do - guard $ not $ null _etdValueDefinitions - Just $ - G.TypeDefinitionEnum - G.EnumTypeDefinition {_etdValueDefinitions = sort _etdValueDefinitions, ..} - G.TypeDefinitionInputObject G.InputObjectTypeDefinition {..} -> do - guard $ not $ null _iotdValueDefinitions - Just $ - G.TypeDefinitionInputObject - G.InputObjectTypeDefinition {_iotdValueDefinitions = sort _iotdValueDefinitions, ..} - -genGraphQLTypeDefinitions :: Gen GraphQLTypeDefinitions -genGraphQLTypeDefinitions = - mkGraphQLTypeDefinitions <$> Gen.nonEmpty defaultRange (Gen.mapMaybe isValidTypeDefinition genTypeDefinition) - genRelationshipCapabilities :: MonadGen m => m RelationshipCapabilities genRelationshipCapabilities = pure RelationshipCapabilities {} @@ -167,8 +110,7 @@ genCapabilities = <*> Gen.maybe genQueryCapabilities <*> Gen.maybe genMutationCapabilities <*> Gen.maybe genSubscriptionCapabilities - <*> Gen.maybe genScalarTypesCapabilities - <*> Gen.maybe genGraphQLTypeDefinitions + <*> genScalarTypesCapabilities <*> Gen.maybe genRelationshipCapabilities <*> Gen.maybe genComparisonCapabilities <*> Gen.maybe genMetricsCapabilities