From cc4567e729dc8c32f4da2c3dfcd62cc96ded6a09 Mon Sep 17 00:00:00 2001 From: Anon Ray Date: Fri, 30 Nov 2018 09:47:12 +0530 Subject: [PATCH 1/5] merge types with same structure in remote schema (fix #1112) --- server/src-lib/Hasura/GraphQL/Schema.hs | 28 ++++++++++++---- .../src-lib/Hasura/GraphQL/Validate/Types.hs | 32 +++++++++++++++++++ server/tests-py/graphql_server.py | 29 ++++++++++++++++- .../remote_schemas/drop_person_table.yaml | 5 +++ .../queries/remote_schemas/person_table.yaml | 14 ++++++++ server/tests-py/test_schema_stitching.py | 17 ++++++++++ 6 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 server/tests-py/queries/remote_schemas/drop_person_table.yaml create mode 100644 server/tests-py/queries/remote_schemas/person_table.yaml diff --git a/server/src-lib/Hasura/GraphQL/Schema.hs b/server/src-lib/Hasura/GraphQL/Schema.hs index 0980673487673..82724cd71da1e 100644 --- a/server/src-lib/Hasura/GraphQL/Schema.hs +++ b/server/src-lib/Hasura/GraphQL/Schema.hs @@ -1582,19 +1582,26 @@ checkSchemaConflicts :: (MonadError QErr m) => GCtx -> GCtx -> m () checkSchemaConflicts gCtx remoteCtx = do - -- check type conflicts let typeMap = _gTypes gCtx -- hasura typemap - hTypes = map G.unNamedType $ Map.keys typeMap + -- check type conflicts + let hTypes = Map.elems typeMap + hTyNames = map G.unNamedType $ Map.keys typeMap rmQRootName = _otiName $ _gQueryRoot remoteCtx rmMRootName = maybeToList $ _otiName <$> _gMutRoot remoteCtx rmRootNames = map G.unNamedType (rmQRootName:rmMRootName) - rmTypes = filter (`notElem` builtinTy ++ rmRootNames) $ - map G.unNamedType $ Map.keys $ _gTypes remoteCtx + rmTypes = Map.filterWithKey + (\k _ -> G.unNamedType k `notElem` builtinTy ++ rmRootNames) + $ _gTypes remoteCtx - conflictedTypes = filter (`elem` hTypes) rmTypes + isTyInfoSame ty = any (\t -> tyinfoEq t ty) hTypes + -- name is same and structure is not same + isSame n ty = G.unNamedType n `elem` hTyNames && + not (isTyInfoSame ty) + conflictedTypes = Map.filterWithKey isSame rmTypes + conflictedTyNames = map G.unNamedType $ Map.keys conflictedTypes - unless (null conflictedTypes) $ - throw400 RemoteSchemaConflicts $ tyMsg conflictedTypes + unless (Map.null conflictedTypes) $ + throw400 RemoteSchemaConflicts $ tyMsg conflictedTyNames -- check node conflicts let rmQRoot = _otiFields $ _gQueryRoot remoteCtx @@ -1615,6 +1622,13 @@ checkSchemaConflicts gCtx remoteCtx = do _ -> return () where + tyinfoEq a b = case (a, b) of + (TIScalar t1, TIScalar t2) -> tyEq t1 t2 + (TIObj t1, TIObj t2) -> tyEq t1 t2 + (TIEnum t1, TIEnum t2) -> tyEq t1 t2 + (TIInpObj t1, TIInpObj t2) -> tyEq t1 t2 + _ -> False + hQRName = G.NamedType "query_root" hMRName = G.NamedType "mutation_root" tyMsg ty = "types: [" <> namesToTxt ty <> diff --git a/server/src-lib/Hasura/GraphQL/Validate/Types.hs b/server/src-lib/Hasura/GraphQL/Validate/Types.hs index dfc3c42d8c5b0..56064e95500bc 100644 --- a/server/src-lib/Hasura/GraphQL/Validate/Types.hs +++ b/server/src-lib/Hasura/GraphQL/Validate/Types.hs @@ -38,6 +38,7 @@ module Hasura.GraphQL.Validate.Types , fromSchemaDocQ , TypeMap , TypeLoc (..) + , TyEq (..) , AnnGValue(..) , AnnGObject , hasNullVal @@ -63,6 +64,10 @@ import Hasura.RQL.Types.RemoteSchema import Hasura.SQL.Types import Hasura.SQL.Value + +class (Eq a) => TyEq a where + tyEq :: a -> a -> Bool + data EnumValInfo = EnumValInfo { _eviDesc :: !(Maybe G.Description) @@ -82,6 +87,9 @@ data EnumTyInfo , _etiLoc :: !TypeLoc } deriving (Show, Eq, TH.Lift) +instance TyEq EnumTyInfo where + tyEq a b = (_etiName a == _etiName b) && (_etiValues a == _etiValues b) + fromEnumTyDef :: G.EnumTypeDefinition -> TypeLoc -> EnumTyInfo fromEnumTyDef (G.EnumTypeDefinition descM n _ valDefs) loc = EnumTyInfo descM (G.NamedType n) enumVals loc @@ -120,6 +128,11 @@ data ObjFldInfo , _fiLoc :: !TypeLoc } deriving (Show, Eq, TH.Lift) +instance TyEq ObjFldInfo where + tyEq a b = (_fiName a == _fiName b) + && (_fiTy a == _fiTy b) + && (_fiParams a == _fiParams b) + fromFldDef :: G.FieldDefinition -> TypeLoc -> ObjFldInfo fromFldDef (G.FieldDefinition descM n args ty _) loc = ObjFldInfo descM n params ty loc @@ -135,6 +148,18 @@ data ObjTyInfo , _otiFields :: !ObjFieldMap } deriving (Show, Eq, TH.Lift) +instance TyEq ObjTyInfo where + -- incase of ObjTyInfo fields from the first Obj are compared with the second + tyEq a b = (_otiName a == _otiName b) && fldsEq + where + aFlds = _otiFields a + bFlds = _otiFields b + fldsEq = any (== True) $ flip map (Map.toList aFlds) $ + \(n, fld) -> + case Map.lookup n bFlds of + Nothing -> False + Just f -> tyEq fld f + instance Monoid ObjTyInfo where mempty = ObjTyInfo Nothing (G.NamedType "") Map.empty @@ -172,6 +197,10 @@ data InpObjTyInfo , _iotiLoc :: !TypeLoc } deriving (Show, Eq, TH.Lift) +instance TyEq InpObjTyInfo where + tyEq a b = (_iotiName a == _iotiName b) + && (_iotiFields a == _iotiFields b) + fromInpObjTyDef :: G.InputObjectTypeDefinition -> TypeLoc -> InpObjTyInfo fromInpObjTyDef (G.InputObjectTypeDefinition descM n _ inpFlds) loc = InpObjTyInfo descM (G.NamedType n) fldMap loc @@ -186,6 +215,9 @@ data ScalarTyInfo , _stiLoc :: !TypeLoc } deriving (Show, Eq, TH.Lift) +instance TyEq ScalarTyInfo where + tyEq a b = _stiType a == _stiType b + fromScalarTyDef :: G.ScalarTypeDefinition -> TypeLoc diff --git a/server/tests-py/graphql_server.py b/server/tests-py/graphql_server.py index dfe8278aec8bd..fd4669a9d2209 100644 --- a/server/tests-py/graphql_server.py +++ b/server/tests-py/graphql_server.py @@ -87,7 +87,6 @@ def resolve_allUsers(self, info): class UserMutation(graphene.ObjectType): createUser = CreateUser.Field() - user_schema = graphene.Schema(query=UserQuery, mutation=UserMutation) class UserGraphQL(RequestHandler): @@ -125,11 +124,39 @@ def post(self, req): res = country_schema.execute(req.json['query']) return mkJSONResp(res) + +class person(graphene.ObjectType): + id = graphene.Int(required=True) + name = graphene.String() + + def resolve_id(self, info): + return 42 + def resolve_name(self, info): + return 'Arthur Dent' + +class PersonQuery(graphene.ObjectType): + person_ = graphene.Field(person) + + def resolve_person_(self, info): + return person() + +person_schema = graphene.Schema(query=PersonQuery) + +class PersonGraphQL(RequestHandler): + def get(self, req): + return Response(HTTPStatus.METHOD_NOT_ALLOWED) + def post(self, req): + if not req.json: + return Response(HTTPStatus.BAD_REQUEST) + res = person_schema.execute(req.json['query']) + return mkJSONResp(res) + handlers = MkHandlers({ '/hello': HelloWorldHandler, '/hello-graphql': HelloGraphQL, '/user-graphql': UserGraphQL, '/country-graphql': CountryGraphQL, + '/person-graphql': PersonGraphQL }) diff --git a/server/tests-py/queries/remote_schemas/drop_person_table.yaml b/server/tests-py/queries/remote_schemas/drop_person_table.yaml new file mode 100644 index 0000000000000..8d11614418aec --- /dev/null +++ b/server/tests-py/queries/remote_schemas/drop_person_table.yaml @@ -0,0 +1,5 @@ +type: bulk +args: +- type: run_sql + args: + sql: "drop table person cascade" diff --git a/server/tests-py/queries/remote_schemas/person_table.yaml b/server/tests-py/queries/remote_schemas/person_table.yaml new file mode 100644 index 0000000000000..2aa8206822b27 --- /dev/null +++ b/server/tests-py/queries/remote_schemas/person_table.yaml @@ -0,0 +1,14 @@ +type: bulk +args: +- type: run_sql + args: + sql: | + CREATE TABLE person ( + id SERIAL PRIMARY KEY, + name TEXT + ); + +- type: track_table + args: + schema: public + name: person diff --git a/server/tests-py/test_schema_stitching.py b/server/tests-py/test_schema_stitching.py index 1889658abc102..1c684d597a669 100644 --- a/server/tests-py/test_schema_stitching.py +++ b/server/tests-py/test_schema_stitching.py @@ -139,6 +139,23 @@ def test_add_schema_duplicate_name(self, hge_ctx): st_code, resp = hge_ctx.v1q(q) assert st_code == 500, resp assert resp['code'] == 'postgres-error' + + def test_add_schema_same_type(self, hge_ctx): + """ + test types get merged when remote schema has type with same name and + same structure + """ + st_code, resp = hge_ctx.v1q_f(self.dir + '/person_table.yaml') + assert st_code == 200, resp + q = mk_add_remote_q('person-graphql', 'http://localhost:5000/person-graphql') + + st_code, resp = hge_ctx.v1q(q) + assert st_code == 200, resp + st_code, resp = hge_ctx.v1q_f(self.dir + '/drop_person_table.yaml') + assert st_code == 200, resp + hge_ctx.v1q({"type": "remove_remote_schema", "args": {"name": "person-graphql"}}) + assert st_code == 200, resp + # def test_remote_query_variables(self, hge_ctx): # pass # def test_add_schema_url_from_env(self, hge_ctx): From 00858add831fbdf1fb827ec133cb52be8bcb7187 Mon Sep 17 00:00:00 2001 From: Anon Ray Date: Fri, 7 Dec 2018 15:33:18 +0530 Subject: [PATCH 2/5] use type families to equate graphql type properties also fix merging subscription root from remote --- server/src-lib/Hasura/GraphQL/Context.hs | 2 +- server/src-lib/Hasura/GraphQL/RemoteServer.hs | 28 +++++++++- server/src-lib/Hasura/GraphQL/Schema.hs | 45 ++++++++++------ .../Hasura/GraphQL/Transport/WebSocket.hs | 2 + .../src-lib/Hasura/GraphQL/Validate/Types.hs | 52 ++++++++++--------- server/tests-py/graphql_server.py | 2 +- 6 files changed, 85 insertions(+), 46 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Context.hs b/server/src-lib/Hasura/GraphQL/Context.hs index dd3a0f30bef93..f4af8dd70ed3b 100644 --- a/server/src-lib/Hasura/GraphQL/Context.hs +++ b/server/src-lib/Hasura/GraphQL/Context.hs @@ -284,7 +284,7 @@ mkGCtx (TyAgg tyInfos fldInfos ordByEnums) (RootFlds flds) insCtxMap = ] <> scalarTys <> compTys <> defaultTypes -- for now subscription root is query root - in GCtx allTys fldInfos ordByEnums queryRoot mutRootM (Just queryRoot) + in GCtx allTys fldInfos ordByEnums queryRoot mutRootM subRootM (Map.map fst flds) insCtxMap where mkMutRoot = diff --git a/server/src-lib/Hasura/GraphQL/RemoteServer.hs b/server/src-lib/Hasura/GraphQL/RemoteServer.hs index d7301e03171fc..a9574c6381793 100644 --- a/server/src-lib/Hasura/GraphQL/RemoteServer.hs +++ b/server/src-lib/Hasura/GraphQL/RemoteServer.hs @@ -55,18 +55,20 @@ fetchRemoteSchema manager name def@(RemoteSchemaInfo url headerConf _) = do introspectRes :: (FromIntrospection IntrospectionResult) <- either schemaErr return $ J.eitherDecode respData - let (G.SchemaDocument tyDefs, qRootN, mRootN, _) = + let (G.SchemaDocument tyDefs, qRootN, mRootN, sRootN) = fromIntrospection introspectRes let etTypeInfos = mapM fromRemoteTyDef tyDefs typeInfos <- either schemaErr return etTypeInfos let typMap = VT.mkTyInfoMap typeInfos mQrTyp = Map.lookup qRootN typMap mMrTyp = maybe Nothing (\mr -> Map.lookup mr typMap) mRootN + mSrTyp = maybe Nothing (\sr -> Map.lookup sr typMap) sRootN qrTyp <- liftMaybe noQueryRoot mQrTyp let mRmQR = VT.getObjTyM qrTyp mRmMR = join $ VT.getObjTyM <$> mMrTyp + mRmSR = join $ VT.getObjTyM <$> mSrTyp rmQR <- liftMaybe (err400 Unexpected "query root has to be an object type") mRmQR - return $ GS.RemoteGCtx typMap rmQR mRmMR Nothing + return $ GS.RemoteGCtx typMap rmQR mRmMR mRmSR where noQueryRoot = err400 Unexpected "query root not found in remote schema" @@ -95,6 +97,7 @@ mkDefaultRemoteGCtx mkDefaultRemoteGCtx = foldlM (\combG -> mergeGCtx combG . convRemoteGCtx) GS.emptyGCtx +-- merge a remote schema `gCtx` into current `gCtxMap` mergeRemoteSchema :: (MonadError QErr m) => GS.GCtxMap @@ -117,10 +120,12 @@ mergeGCtx gCtx rmMergedGCtx = do GS.checkSchemaConflicts gCtx rmMergedGCtx let newQR = mergeQueryRoot gCtx rmMergedGCtx newMR = mergeMutRoot gCtx rmMergedGCtx + newSR = mergeSubRoot gCtx rmMergedGCtx newTyMap = mergeTyMaps hsraTyMap rmTypes newQR newMR updatedGCtx = gCtx { GS._gTypes = newTyMap , GS._gQueryRoot = newQR , GS._gMutRoot = newMR + , GS._gSubRoot = newSR } return updatedGCtx @@ -129,6 +134,7 @@ convRemoteGCtx rmGCtx = GS.emptyGCtx { GS._gTypes = GS._rgTypes rmGCtx , GS._gQueryRoot = GS._rgQueryRoot rmGCtx , GS._gMutRoot = GS._rgMutationRoot rmGCtx + , GS._gSubRoot = GS._rgSubscriptionRoot rmGCtx } @@ -156,6 +162,24 @@ mkNewMutRoot :: VT.ObjFieldMap -> VT.ObjTyInfo mkNewMutRoot flds = VT.ObjTyInfo (Just "mutation root") (G.NamedType "mutation_root") flds +mergeSubRoot :: GS.GCtx -> GS.GCtx -> Maybe VT.ObjTyInfo +mergeSubRoot a b = + let objA' = fromMaybe mempty $ GS._gSubRoot a + objB = fromMaybe mempty $ GS._gSubRoot b + objA = newRootOrEmpty objA' objB + merged = objA <> objB + in bool (Just merged) Nothing $ merged == mempty + where + newRootOrEmpty x y = + if x == mempty && y /= mempty + then mkNewEmptySubRoot + else x + +mkNewEmptySubRoot :: VT.ObjTyInfo +mkNewEmptySubRoot = VT.ObjTyInfo (Just "subscription root") + (G.NamedType "subscription_root") Map.empty + + mergeTyMaps :: VT.TypeMap -> VT.TypeMap diff --git a/server/src-lib/Hasura/GraphQL/Schema.hs b/server/src-lib/Hasura/GraphQL/Schema.hs index 82724cd71da1e..de0bef7f777c2 100644 --- a/server/src-lib/Hasura/GraphQL/Schema.hs +++ b/server/src-lib/Hasura/GraphQL/Schema.hs @@ -1586,10 +1586,12 @@ checkSchemaConflicts gCtx remoteCtx = do -- check type conflicts let hTypes = Map.elems typeMap hTyNames = map G.unNamedType $ Map.keys typeMap + -- get the root names from the remote schema rmQRootName = _otiName $ _gQueryRoot remoteCtx rmMRootName = maybeToList $ _otiName <$> _gMutRoot remoteCtx - rmRootNames = map G.unNamedType (rmQRootName:rmMRootName) - rmTypes = Map.filterWithKey + rmSRootName = maybeToList $ _otiName <$> _gSubRoot remoteCtx + rmRootNames = map G.unNamedType (rmQRootName:(rmMRootName ++ rmSRootName)) + let rmTypes = Map.filterWithKey (\k _ -> G.unNamedType k `notElem` builtinTy ++ rmRootNames) $ _gTypes remoteCtx @@ -1623,10 +1625,10 @@ checkSchemaConflicts gCtx remoteCtx = do where tyinfoEq a b = case (a, b) of - (TIScalar t1, TIScalar t2) -> tyEq t1 t2 - (TIObj t1, TIObj t2) -> tyEq t1 t2 - (TIEnum t1, TIEnum t2) -> tyEq t1 t2 - (TIInpObj t1, TIInpObj t2) -> tyEq t1 t2 + (TIScalar t1, TIScalar t2) -> typeEq t1 t2 + (TIObj t1, TIObj t2) -> typeEq t1 t2 + (TIEnum t1, TIEnum t2) -> typeEq t1 t2 + (TIInpObj t1, TIInpObj t2) -> typeEq t1 t2 _ -> False hQRName = G.NamedType "query_root" @@ -1759,14 +1761,23 @@ mergeMaybeMaps m1 m2 = case (m1, m2) of -- pretty print GCtx -ppGCtx :: GCtx -> IO () -ppGCtx gCtx = do - let types = map (G.unName . G.unNamedType) $ Map.keys $ _gTypes gCtx - qRoot = map G.unName $ Map.keys $ _otiFields $ _gQueryRoot gCtx - mRoot = maybe [] (map G.unName . Map.keys . _otiFields) $ _gMutRoot gCtx - - print ("GCtx [" :: Text) - print $ " types = " <> show types - print $ " query root = " <> show qRoot - print $ " mutation root = " <> show mRoot - print ("]" :: Text) +ppGCtx :: GCtx -> String +ppGCtx gCtx = + "GCtx [" + <> "\n types = " <> show types + <> "\n query root = " <> show qRoot + <> "\n mutation root = " <> show mRoot + <> "\n subscription root = " <> show sRoot + <> "\n]" + + where + types = map (G.unName . G.unNamedType) $ Map.keys $ _gTypes gCtx + qRoot = (,) (_otiName qRootO) $ + map G.unName $ Map.keys $ _otiFields qRootO + mRoot = (,) (_otiName <$> mRootO) $ + maybe [] (map G.unName . Map.keys . _otiFields) mRootO + sRoot = (,) (_otiName <$> sRootO) $ + maybe [] (map G.unName . Map.keys . _otiFields) sRootO + qRootO = _gQueryRoot gCtx + mRootO = _gMutRoot gCtx + sRootO = _gSubRoot gCtx diff --git a/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs b/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs index 39fe92e73e956..8b11c27b75b92 100644 --- a/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs +++ b/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs @@ -197,6 +197,8 @@ onStart serverEnv wsConn (StartMsg opId q) msgRaw = catchAndIgnore $ do VT.HasuraType -> runHasuraQ userInfo gCtx queryParts VT.RemoteType _ rsi -> do + when (G._todType opDef == G.OperationTypeSubscription) $ + withComplete $ sendConnErr "subscription to remote server is not supported" resp <- runExceptT $ TH.runRemoteGQ httpMgr userInfo reqHdrs msgRaw rsi opDef either postExecErr sendSuccResp resp diff --git a/server/src-lib/Hasura/GraphQL/Validate/Types.hs b/server/src-lib/Hasura/GraphQL/Validate/Types.hs index 56064e95500bc..9a86c46a8abaf 100644 --- a/server/src-lib/Hasura/GraphQL/Validate/Types.hs +++ b/server/src-lib/Hasura/GraphQL/Validate/Types.hs @@ -5,6 +5,7 @@ {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} module Hasura.GraphQL.Validate.Types ( InpValInfo(..) @@ -38,7 +39,8 @@ module Hasura.GraphQL.Validate.Types , fromSchemaDocQ , TypeMap , TypeLoc (..) - , TyEq (..) + --, TyEq (..) + , typeEq , AnnGValue(..) , AnnGObject , hasNullVal @@ -65,8 +67,14 @@ import Hasura.SQL.Types import Hasura.SQL.Value -class (Eq a) => TyEq a where - tyEq :: a -> a -> Bool +-- | Typeclass for equating relevant properties of various GraphQL types +-- | defined below +class EquatableGType a where + type EqProps a + getEqProps :: a -> EqProps a + +typeEq :: (Show (EqProps a), EquatableGType a, Eq (EqProps a)) => a -> a -> Bool +typeEq a b = getEqProps a == getEqProps b data EnumValInfo = EnumValInfo @@ -87,8 +95,9 @@ data EnumTyInfo , _etiLoc :: !TypeLoc } deriving (Show, Eq, TH.Lift) -instance TyEq EnumTyInfo where - tyEq a b = (_etiName a == _etiName b) && (_etiValues a == _etiValues b) +instance EquatableGType EnumTyInfo where + type EqProps EnumTyInfo = (G.NamedType, Map.HashMap G.EnumValue EnumValInfo) + getEqProps ety = (,) (_etiName ety) (_etiValues ety) fromEnumTyDef :: G.EnumTypeDefinition -> TypeLoc -> EnumTyInfo fromEnumTyDef (G.EnumTypeDefinition descM n _ valDefs) loc = @@ -128,10 +137,9 @@ data ObjFldInfo , _fiLoc :: !TypeLoc } deriving (Show, Eq, TH.Lift) -instance TyEq ObjFldInfo where - tyEq a b = (_fiName a == _fiName b) - && (_fiTy a == _fiTy b) - && (_fiParams a == _fiParams b) +instance EquatableGType ObjFldInfo where + type EqProps ObjFldInfo = (G.Name, G.GType, ParamMap) + getEqProps o = (,,) (_fiName o) (_fiTy o) (_fiParams o) fromFldDef :: G.FieldDefinition -> TypeLoc -> ObjFldInfo fromFldDef (G.FieldDefinition descM n args ty _) loc = @@ -148,17 +156,10 @@ data ObjTyInfo , _otiFields :: !ObjFieldMap } deriving (Show, Eq, TH.Lift) -instance TyEq ObjTyInfo where - -- incase of ObjTyInfo fields from the first Obj are compared with the second - tyEq a b = (_otiName a == _otiName b) && fldsEq - where - aFlds = _otiFields a - bFlds = _otiFields b - fldsEq = any (== True) $ flip map (Map.toList aFlds) $ - \(n, fld) -> - case Map.lookup n bFlds of - Nothing -> False - Just f -> tyEq fld f +instance EquatableGType ObjTyInfo where + type EqProps ObjTyInfo = + (G.NamedType, Map.HashMap G.Name (G.Name, G.GType, ParamMap)) + getEqProps a = (,) (_otiName a) (Map.map getEqProps (_otiFields a)) instance Monoid ObjTyInfo where mempty = ObjTyInfo Nothing (G.NamedType "") Map.empty @@ -197,9 +198,9 @@ data InpObjTyInfo , _iotiLoc :: !TypeLoc } deriving (Show, Eq, TH.Lift) -instance TyEq InpObjTyInfo where - tyEq a b = (_iotiName a == _iotiName b) - && (_iotiFields a == _iotiFields b) +instance EquatableGType InpObjTyInfo where + type EqProps InpObjTyInfo = (G.NamedType, InpObjFldMap) + getEqProps a = (,) (_iotiName a) (_iotiFields a) fromInpObjTyDef :: G.InputObjectTypeDefinition -> TypeLoc -> InpObjTyInfo fromInpObjTyDef (G.InputObjectTypeDefinition descM n _ inpFlds) loc = @@ -215,8 +216,9 @@ data ScalarTyInfo , _stiLoc :: !TypeLoc } deriving (Show, Eq, TH.Lift) -instance TyEq ScalarTyInfo where - tyEq a b = _stiType a == _stiType b +instance EquatableGType ScalarTyInfo where + type EqProps ScalarTyInfo = PGColType + getEqProps a = _stiType a fromScalarTyDef :: G.ScalarTypeDefinition diff --git a/server/tests-py/graphql_server.py b/server/tests-py/graphql_server.py index fd4669a9d2209..b6eee26c34cb8 100644 --- a/server/tests-py/graphql_server.py +++ b/server/tests-py/graphql_server.py @@ -24,7 +24,7 @@ class Hello(graphene.ObjectType): def resolve_hello(self, info, arg): return "Hello " + arg -hello_schema = graphene.Schema(query=Hello) +hello_schema = graphene.Schema(query=Hello, subscription=Hello) class HelloGraphQL(RequestHandler): def get(self, request): From 85c2994a0711c3037ba446cc5fe2febc21527849 Mon Sep 17 00:00:00 2001 From: Anon Ray Date: Wed, 12 Dec 2018 11:41:57 +0530 Subject: [PATCH 3/5] remove comment and unnecessary show constraint --- server/src-lib/Hasura/GraphQL/Validate/Types.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Validate/Types.hs b/server/src-lib/Hasura/GraphQL/Validate/Types.hs index 9a86c46a8abaf..dacad7e88fed5 100644 --- a/server/src-lib/Hasura/GraphQL/Validate/Types.hs +++ b/server/src-lib/Hasura/GraphQL/Validate/Types.hs @@ -39,7 +39,6 @@ module Hasura.GraphQL.Validate.Types , fromSchemaDocQ , TypeMap , TypeLoc (..) - --, TyEq (..) , typeEq , AnnGValue(..) , AnnGObject @@ -73,7 +72,7 @@ class EquatableGType a where type EqProps a getEqProps :: a -> EqProps a -typeEq :: (Show (EqProps a), EquatableGType a, Eq (EqProps a)) => a -> a -> Bool +typeEq :: (EquatableGType a, Eq (EqProps a)) => a -> a -> Bool typeEq a b = getEqProps a == getEqProps b data EnumValInfo From c06249d9a26de895443cd5e2c7df89b4eca7b0fd Mon Sep 17 00:00:00 2001 From: Anon Ray Date: Wed, 12 Dec 2018 14:56:18 +0530 Subject: [PATCH 4/5] don't compare the description of fields of InpObjTyInfo --- server/src-lib/Hasura/GraphQL/Validate/Types.hs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/server/src-lib/Hasura/GraphQL/Validate/Types.hs b/server/src-lib/Hasura/GraphQL/Validate/Types.hs index dacad7e88fed5..2053e60008d51 100644 --- a/server/src-lib/Hasura/GraphQL/Validate/Types.hs +++ b/server/src-lib/Hasura/GraphQL/Validate/Types.hs @@ -102,7 +102,7 @@ fromEnumTyDef :: G.EnumTypeDefinition -> TypeLoc -> EnumTyInfo fromEnumTyDef (G.EnumTypeDefinition descM n _ valDefs) loc = EnumTyInfo descM (G.NamedType n) enumVals loc where - enumVals = Map.fromList $ + enumVals = Map.fromList [(G._evdName valDef, fromEnumValDef valDef) | valDef <- valDefs] data InpValInfo @@ -113,6 +113,10 @@ data InpValInfo -- TODO, handle default values } deriving (Show, Eq, TH.Lift) +instance EquatableGType InpValInfo where + type EqProps InpValInfo = (G.Name, G.GType) + getEqProps ity = (,) (_iviName ity) (_iviType ity) + fromInpValDef :: G.InputValueDefinition -> InpValInfo fromInpValDef (G.InputValueDefinition descM n ty _) = InpValInfo descM n ty @@ -198,8 +202,8 @@ data InpObjTyInfo } deriving (Show, Eq, TH.Lift) instance EquatableGType InpObjTyInfo where - type EqProps InpObjTyInfo = (G.NamedType, InpObjFldMap) - getEqProps a = (,) (_iotiName a) (_iotiFields a) + type EqProps InpObjTyInfo = (G.NamedType, Map.HashMap G.Name (G.Name, G.GType)) + getEqProps a = (,) (_iotiName a) (Map.map getEqProps $ _iotiFields a) fromInpObjTyDef :: G.InputObjectTypeDefinition -> TypeLoc -> InpObjTyInfo fromInpObjTyDef (G.InputObjectTypeDefinition descM n _ inpFlds) loc = @@ -217,7 +221,7 @@ data ScalarTyInfo instance EquatableGType ScalarTyInfo where type EqProps ScalarTyInfo = PGColType - getEqProps a = _stiType a + getEqProps = _stiType fromScalarTyDef :: G.ScalarTypeDefinition From 59582c6bf4bc7182fe473b47393e029cb8e81f54 Mon Sep 17 00:00:00 2001 From: Anon Ray Date: Wed, 12 Dec 2018 15:18:56 +0530 Subject: [PATCH 5/5] update docs about merging types [skip ci] --- docs/graphql/manual/remote-schemas/index.rst | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/graphql/manual/remote-schemas/index.rst b/docs/graphql/manual/remote-schemas/index.rst index db007ae0c7824..284e927ea7e3b 100644 --- a/docs/graphql/manual/remote-schemas/index.rst +++ b/docs/graphql/manual/remote-schemas/index.rst @@ -30,10 +30,22 @@ Use-cases - Querying data that is not available in your database -You can handle these use-cases by writing resolvers in a custom GraphQL server and making Hasura merge this ``remote schema`` with the existing autogenerated schema. You can also add multiple remote schemas. Think of the merged schema as a union of top-level nodes from each of the sub-schemas. +You can handle these use-cases by writing resolvers in a custom GraphQL server +and making Hasura merge this ``remote schema`` with the existing autogenerated +schema. You can also add multiple remote schemas. Think of the merged schema as +a union of top-level nodes from each of the sub-schemas. + +Note that if you are looking for adding authorization & access control for your +app users to the GraphQL APIs that are auto-generated via Hasura, head to +:doc:`Authorization / Access control <../auth/index>` + +.. note:: + + **Nomenclature**: + + Top-level node names need to be unique across all merged schemas (*case-sensitive match*). + Types with the *exact same name and structure* will be merged. But types with *same name but different structure* will result in type conflicts. -Note that if you are looking for adding authorization & access control for your app users -to the GraphQL APIs that are auto-generated via Hasura, head to :doc:`Authorization / Access control <../auth/index>` How to add a remote schema -------------------------- @@ -51,11 +63,11 @@ is to use one of our boilerplates: - `Boilerplates `__ - `Serverless boilerplates `__ + .. note:: **Current limitations**: - - Nomenclature: Type names and node names need to be unique across all merged schemas (*case-sensitive match*). In the next few iterations, support for merging types with the exact same name and structure will be available. - Nodes from different GraphQL servers cannot be used in the same query/mutation. All top-level nodes have to be from the same GraphQL server. - Subscriptions on remote GraphQL server are not supported. - Interfaces_ and Unions_ are not supported - if a remote schema has interfaces/unions, an error will be thrown if you try to merge it.