server: support remote relationship with ID scalar types (#92)

Co-authored-by: Karthikeyan Chinnakonda <karthikeyan@hasura.io>
GITHUB_PR_NUMBER: 6227
GITHUB_PR_URL: https://github.com/hasura/graphql-engine/pull/6227

Co-authored-by: Karthikeyan Chinnakonda <karthikeyan@hasura.io>
GitOrigin-RevId: 666ec37d570e46482e0f27db2c97c9e3f9c1f4d3
This commit is contained in:
hasura-bot 2020-11-24 13:19:16 +01:00
parent 2acad94fb1
commit 5ca8cd23a0
3 changed files with 29 additions and 21 deletions

View File

@ -61,6 +61,7 @@ This release contains the [PDV refactor (#4111)](https://github.com/hasura/graph
- server: Configurable websocket keep-alive interval. Add `--websocket-keepalive` command-line flag and `HASURA_GRAPHQL_WEBSOCKET_KEEPALIVE` env variable (fix #3539)
- server: validate remote schema queries (fixes #4143)
- server: introduce optional custom table name in table configuration to track the table according to the custom name. The `set_table_custom_fields` API has been deprecated, A new API `set_table_customization` has been added to set the configuration. (#3811)
- server: support joining Int or String scalar types to ID scalar type in remote relationship
- console: allow user to cascade Postgres dependencies when dropping Postgres objects (close #5109) (#5248)
- console: mark inconsistent remote schemas in the UI (close #5093) (#5181)
- console: remove ONLY as default for ALTER TABLE in column alter operations (close #5512) #5706

View File

@ -42,6 +42,7 @@ data ValidationError
| UnsupportedMultipleElementLists
| UnsupportedEnum
| InvalidGraphQLName !Text
| IDTypeJoin !G.Name
errorToText :: ValidationError -> Text
errorToText = \case
@ -77,6 +78,8 @@ errorToText = \case
"enum value is not supported"
InvalidGraphQLName t ->
t <<> " is not a valid GraphQL identifier"
IDTypeJoin typeName ->
"Only ID, Int, uuid or String scalar types can be joined to the ID type, but recieved " <>> typeName
-- | Validate a remote relationship given a context.
validateRemoteRelationship
@ -281,10 +284,10 @@ renameNamedType rename =
G.unsafeMkName . rename . G.unName
-- | Convert a field name to a variable name.
pgColumnToVariable :: (MonadError ValidationError m) => PGCol -> m G.Name
pgColumnToVariable :: MonadError ValidationError m => PGCol -> m G.Name
pgColumnToVariable pgCol =
let pgColText = getPGColTxt pgCol
in maybe (throwError $ InvalidGraphQLName pgColText) pure $ G.mkName pgColText
in onNothing (G.mkName pgColText) (throwError $ InvalidGraphQLName pgColText)
-- | Lookup the field in the schema.
lookupField
@ -342,17 +345,17 @@ validateType permittedVariables value expectedGType schemaDocument =
namedType <- columnInfoToNamedType fieldInfo
isTypeCoercible (mkGraphQLType namedType) expectedGType
G.VInt {} -> do
intScalarGType <- mkGraphQLType <$> mkScalarTy PGInteger
intScalarGType <- mkGraphQLType <$> getPGScalarTypeName PGInteger
isTypeCoercible intScalarGType expectedGType
G.VFloat {} -> do
floatScalarGType <- mkGraphQLType <$> mkScalarTy PGFloat
floatScalarGType <- mkGraphQLType <$> getPGScalarTypeName PGFloat
isTypeCoercible floatScalarGType expectedGType
G.VBoolean {} -> do
boolScalarGType <- mkGraphQLType <$> mkScalarTy PGBoolean
boolScalarGType <- mkGraphQLType <$> getPGScalarTypeName PGBoolean
isTypeCoercible boolScalarGType expectedGType
G.VNull -> throwError NullNotAllowedHere
G.VString {} -> do
stringScalarGType <- mkGraphQLType <$> mkScalarTy PGText
stringScalarGType <- mkGraphQLType <$> getPGScalarTypeName PGText
isTypeCoercible stringScalarGType expectedGType
G.VEnum _ -> throwError UnsupportedEnum
G.VList values -> do
@ -389,12 +392,6 @@ validateType permittedVariables value expectedGType schemaDocument =
mkGraphQLType =
G.TypeNamed (G.Nullability False)
mkScalarTy scalarType = do
eitherScalar <- runExceptT $ mkScalarTypeName scalarType
case eitherScalar of
Left _ -> throwError $ InvalidGraphQLName $ toSQLTxt scalarType
Right s -> pure s
isTypeCoercible
:: (MonadError ValidationError m)
=> G.GType
@ -409,7 +406,16 @@ isTypeCoercible actualType expectedType =
let (actualBaseType, actualNestingLevel) = getBaseTyWithNestedLevelsCount actualType
(expectedBaseType, expectedNestingLevel) = getBaseTyWithNestedLevelsCount expectedType
in
if | actualBaseType /= expectedBaseType -> raiseValidationError
if | expectedBaseType == $$(G.litName "ID") ->
bool (throwError $ IDTypeJoin actualBaseType)
(pure ())
-- Check under `Input Coercion` https://spec.graphql.org/June2018/#sec-ID
-- We can also include the `ID` type in the below list but it will be
-- extraneous because at the time of writing this, we don't generate
-- the `ID` type in the DB schema
(G.unName actualBaseType `elem`
["ID", "Int", "String", "bigint", "smallint" , "uuid"])
| actualBaseType /= expectedBaseType -> raiseValidationError
-- we cannot coerce two types with different nesting levels,
-- for example, we cannot coerce [Int] to [[Int]]
| (actualNestingLevel == expectedNestingLevel || actualNestingLevel == 0) -> pure ()
@ -417,6 +423,11 @@ isTypeCoercible actualType expectedType =
where
raiseValidationError = throwError $ ExpectedTypeButGot expectedType actualType
getPGScalarTypeName :: MonadError ValidationError m => PGScalarType -> m G.Name
getPGScalarTypeName scalarType =
runExceptT (mkScalarTypeName scalarType) >>=
flip onLeft (\ _ -> throwError $ InvalidGraphQLName $ toSQLTxt scalarType)
assertListType :: (MonadError ValidationError m) => G.GType -> m ()
assertListType actualType =
unless (G.isListType actualType)
@ -429,9 +440,5 @@ columnInfoToNamedType
-> m G.Name
columnInfoToNamedType pci =
case pgiType pci of
PGColumnScalar scalarType -> do
eitherScalar <- runExceptT $ mkScalarTypeName scalarType
case eitherScalar of
Left _ -> throwError $ InvalidGraphQLName $ toSQLTxt scalarType
Right s -> pure s
PGColumnScalar scalarType -> getPGScalarTypeName scalarType
_ -> throwError UnsupportedEnum

View File

@ -74,12 +74,12 @@ const typeDefs = gql`
type Query {
hello: String
messages(where: MessageWhereInpObj, includes: IncludeInpObj): [Message]
user(user_id: Int!): User
users(user_ids: [Int]!): [User]
user(user_id: ID!): User
users(user_ids: [ID]!): [User]
message(id: Int!) : Message
communications(id: Int): [Communication]
search(id: Int!): SearchResult
getOccupation(name: String!): Occupation!
getOccupation(name: ID!): Occupation!
}
`;