Skip to content

Commit

Permalink
[skip ci] add relay modern support (#4458)
Browse files Browse the repository at this point in the history
* validation support for unions and interfaces

* refactor SQL generation logic for improved readability

* '/v1/relay' endpoint for relay schema

* implement 'Node' interface and top level 'node' field resolver

* add relay toggle on graphiql

* fix explain api response & index plan id with query type

* add hasura mutations to relay

* add relay pytests

* update CHANGELOG.md

Co-authored-by: rakeshkky <12475069+rakeshkky@users.noreply.github.com>
Co-authored-by: Rishichandra Wawhal <rishi@hasura.io>
Co-authored-by: Rikin Kachhia <54616969+rikinsk@users.noreply.github.com>
  • Loading branch information
4 people committed Jul 7, 2020
1 parent 62936cc commit ab65b39
Show file tree
Hide file tree
Showing 67 changed files with 5,434 additions and 1,373 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

## Next release


### Bug fixes and improvements

(Add entries here in the order of: server, console, cli, docs, others)
Expand Down Expand Up @@ -97,7 +96,7 @@ Read more about the session argument for computed fields in the [docs](https://h
A new `seeds` command is introduced in CLI, this will allow managing seed migrations as SQL files

#### Creating seed
```
```
# create a new seed file and use editor to add SQL content
hasura seed create new_table_seed
Expand Down
4 changes: 1 addition & 3 deletions console/src/components/Common/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
import Tooltip from 'react-bootstrap/lib/Tooltip';
import styles from './Tooltip.scss';

const tooltipGen = (message: string) => {
return <Tooltip id={message}>{message}</Tooltip>;
};
Expand All @@ -28,5 +27,4 @@ const ToolTip: React.FC<TooltipProps> = ({
)}
</OverlayTrigger>
);

export default ToolTip;
export default ToolTip;
1 change: 1 addition & 0 deletions server/graphql-engine.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ library
, ghc-heap-view

, directory
, semigroups >= 0.19.1

exposed-modules: Control.Arrow.Extended
, Control.Arrow.Trans
Expand Down
12 changes: 6 additions & 6 deletions server/src-lib/Data/HashMap/Strict/InsOrd/Extended.hs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
module Data.HashMap.Strict.InsOrd.Extended
( OMap.elems
( module OMap
, groupTuples
, groupListWith
) where

import qualified Data.HashMap.Strict.InsOrd as OMap
import Data.HashMap.Strict.InsOrd as OMap
import qualified Data.List as L
import qualified Data.Sequence.NonEmpty as NE

import Data.Hashable (Hashable)
import Data.List (foldl')

import Prelude (Eq, Foldable, Functor, fmap, ($))
import Prelude (Eq, Foldable, Functor, fmap, undefined, ($))

groupTuples
:: (Eq k, Hashable k, Foldable t)
=> t (k, v) -> OMap.InsOrdHashMap k (NE.NESeq v)
groupTuples =
foldl' groupFlds OMap.empty
L.foldl' groupFlds OMap.empty
where
groupFlds m (k, v) =
OMap.insertWith (\_ c -> c NE.|> v) k (NE.singleton v) m
OMap.insertWith (\_ c -> c `undefined` v) k (NE.singleton v) m

groupListWith
:: (Eq k, Hashable k, Foldable t, Functor t)
Expand Down
11 changes: 0 additions & 11 deletions server/src-lib/Data/Sequence/NonEmpty.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ module Data.Sequence.NonEmpty
( NESeq
, pattern (:<||)
, pattern (:||>)
, (<|)
, (|>)
, singleton
, head
, tail
Expand All @@ -22,9 +20,6 @@ import Data.Aeson
import Data.Foldable
import GHC.Generics (Generic)

infixr 5 <|
infixl 5 |>

data NESeq a = NESeq
{ head :: a
, tail :: Seq.Seq a
Expand Down Expand Up @@ -59,12 +54,6 @@ instance ToJSON a => ToJSON (NESeq a) where
singleton :: a -> NESeq a
singleton a = NESeq a Seq.empty

(|>) :: NESeq a -> a -> NESeq a
NESeq h l |> v = NESeq h (l Seq.|> v)

(<|) :: a -> NESeq a -> NESeq a
v <| NESeq h l = NESeq v (h Seq.<| l)

toSeq :: NESeq a -> Seq.Seq a
toSeq (NESeq v l) = v Seq.<| l

Expand Down
6 changes: 3 additions & 3 deletions server/src-lib/Hasura/GraphQL/Context.hs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ traverseAction f = \case
RFRaw x -> pure $ RFRaw x

data QueryDB v
= QDBSimple (RQL.AnnSimpleSelG v)
| QDBPrimaryKey (RQL.AnnSimpleSelG v)
| QDBAggregation (RQL.AnnAggSelG v)
= QDBSimple (RQL.AnnSimpleSelG v)
| QDBPrimaryKey (RQL.AnnSimpleSelG v)
| QDBAggregation (RQL.AnnAggregateSelectG v)

data ActionQuery v
= AQQuery !(RQL.AnnActionExecution v)
Expand Down
11 changes: 5 additions & 6 deletions server/src-lib/Hasura/GraphQL/Execute.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ import Hasura.Prelude
import Hasura.RQL.DDL.Headers
import Hasura.RQL.Types
import Hasura.Server.Utils (RequestId, mkClientHeadersForward,
mkSetCookieHeaders,
userRoleHeader)
mkSetCookieHeaders, userRoleHeader)
import Hasura.Server.Version (HasVersion)
import Hasura.Session

Expand Down Expand Up @@ -142,8 +141,8 @@ getExecPlanPartial userInfo sc enableAL queryType req = do
getGCtx :: C.GQLContext
getGCtx =
case Map.lookup roleName contextMap of
Nothing -> defaultContext
Just gql -> gql
Nothing -> defaultContext
Just gql -> gql
-- TODO FIXME implement backend-only field access
{-
Just (RoleContext defaultGCtx maybeBackendGCtx) ->
Expand Down Expand Up @@ -206,7 +205,7 @@ getResolvedExecPlan
getResolvedExecPlan pgExecCtx planCache userInfo sqlGenCtx
enableAL sc scVer queryType httpManager reqHeaders reqUnparsed = do
planM <- liftIO $ EP.getPlan scVer (_uiRole userInfo)
opNameM queryStr planCache
opNameM queryStr queryType planCache
let usrVars = _uiSession userInfo
case planM of
-- plans are only for queries and subscriptions
Expand Down Expand Up @@ -246,7 +245,7 @@ getResolvedExecPlan pgExecCtx planCache userInfo sqlGenCtx
G.TypedOperationDefinition G.OperationTypeMutation _ varDefs _ selSet -> do
-- (Here the above fragment inlining is actually executed.)
inlinedSelSet <- EI.inlineSelectionSet fragments selSet
queryTx <- EM.convertMutationSelectionSet gCtx (_uiSession userInfo) httpManager reqHeaders
queryTx <- EM.convertMutationSelectionSet gCtx userInfo httpManager reqHeaders
inlinedSelSet varDefs (_grVariables reqUnparsed)
-- traverse_ (addPlanToCache . EP.RPQuery) plan
return $ MutationExecutionPlan $ queryTx
Expand Down
21 changes: 12 additions & 9 deletions server/src-lib/Hasura/GraphQL/Execute/LiveQuery/Plan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ import qualified Data.Aeson.Casing as J
import qualified Data.Aeson.Extended as J
import qualified Data.Aeson.TH as J
import qualified Data.HashMap.Strict as Map
import qualified Data.HashMap.Strict.InsOrd as OMap
import qualified Data.HashMap.Strict.InsOrd as OMap
import qualified Data.Sequence as Seq
import qualified Data.Text as T
import qualified Data.UUID.V4 as UUID
import qualified Database.PG.Query as Q
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Data.HashMap.Strict.InsOrd as OMap
import qualified Data.Sequence as Seq

-- remove these when array encoding is merged
import qualified Database.PG.Query.PTI as PTI
Expand All @@ -43,24 +44,25 @@ import qualified PostgreSQL.Binary.Encoding as PE
import Control.Lens
import Data.UUID (UUID)

import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
import qualified Hasura.GraphQL.Parser.Schema as PS
import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
import qualified Hasura.RQL.DML.Select as DS
import qualified Hasura.SQL.DML as S
--import qualified Hasura.GraphQL.Execute.Query as GEQ

import Hasura.Db
import Hasura.EncJSON
import Hasura.GraphQL.Parser.Column
import Hasura.GraphQL.Context
import Hasura.GraphQL.Execute.Prepare
import Hasura.GraphQL.Execute.Query
import Hasura.RQL.Types
import Hasura.GraphQL.Parser.Column
import Hasura.GraphQL.Resolve.Action
import Hasura.RQL.Types
import Hasura.Server.Version (HasVersion)
import Hasura.Session
import Hasura.SQL.Error
import Hasura.SQL.Types
import Hasura.SQL.Value
import Hasura.GraphQL.Context
import Hasura.Session

-- -------------------------------------------------------------------------------------------------
-- Multiplexed queries
Expand All @@ -72,7 +74,7 @@ toSQLSelect :: SubscriptionRootFieldResolved -> S.Select
toSQLSelect = \case
RFDB (QDBPrimaryKey s) -> DS.mkSQLSelect DS.JASSingleObject s
RFDB (QDBSimple s) -> DS.mkSQLSelect DS.JASMultipleRows s
RFDB (QDBAggregation s) -> DS.mkAggSelect s
RFDB (QDBAggregation s) -> DS.mkAggregateSelect s
RFAction s -> DS.mkSQLSelect DS.JASSingleObject s
-- QRFActionSelect s -> DS.mkSQLSelect DS.JASSingleObject s
-- QRFActionExecuteObject s -> DS.mkSQLSelect DS.JASSingleObject s
Expand Down Expand Up @@ -279,6 +281,7 @@ $(J.deriveToJSON (J.aesonDrop 4 J.snakeCase) ''ReusableLiveQueryPlan)
buildLiveQueryPlan
:: ( MonadError QErr m
, MonadIO m
, HasVersion
)
=> PGExecCtx
-> UserInfo
Expand Down Expand Up @@ -310,7 +313,7 @@ buildLiveQueryPlan pgExecCtx userInfo unpreparedAST = do
(preparedAST, (queryVariableValues, querySyntheticVariableValues)) <- flip runStateT (mempty, Seq.empty) $
for unpreparedAST \unpreparedQuery -> do
traverseQueryRootField resolveMultiplexedValue unpreparedQuery
>>= traverseAction (DS.traverseAnnSimpleSel resolveMultiplexedValue . resolveAsyncActionQuery userInfo)
>>= traverseAction (DS.traverseAnnSimpleSelect resolveMultiplexedValue . resolveAsyncActionQuery userInfo)



Expand Down
27 changes: 14 additions & 13 deletions server/src-lib/Hasura/GraphQL/Execute/Mutation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -67,37 +67,38 @@ convertMutationRootField
, MonadIO m
, MonadError QErr m
)
=> SessionVariables
=> UserInfo
-> HTTP.Manager
-> HTTP.RequestHeaders
-> Bool
-> MutationRootField UnpreparedValue
-> m (Either (LazyRespTx, HTTP.ResponseHeaders) RemoteField)
convertMutationRootField usrVars manager reqHeaders stringifyNum = \case
RFDB (MDBInsert s) -> noResponseHeaders $ convertInsert usrVars s stringifyNum
RFDB (MDBUpdate s) -> noResponseHeaders $ convertUpdate usrVars s stringifyNum
RFDB (MDBDelete s) -> noResponseHeaders $ convertDelete usrVars s stringifyNum
convertMutationRootField userInfo manager reqHeaders stringifyNum = \case
RFDB (MDBInsert s) -> noResponseHeaders $ convertInsert userSession s stringifyNum
RFDB (MDBUpdate s) -> noResponseHeaders $ convertUpdate userSession s stringifyNum
RFDB (MDBDelete s) -> noResponseHeaders $ convertDelete userSession s stringifyNum
RFRemote remote -> pure $ Right remote
RFAction (AMSync s) -> Left <$> first liftTx <$> resolveActionExecution s actionExecContext
RFAction (AMAsync s) -> noResponseHeaders =<< resolveActionMutationAsync s reqHeaders usrVars
RFAction (AMSync s) -> Left <$> first liftTx <$> resolveActionExecution userInfo s actionExecContext
RFAction (AMAsync s) -> noResponseHeaders =<< resolveActionMutationAsync s reqHeaders userSession
RFRaw s -> noResponseHeaders $ pure $ encJFromJValue s
where
noResponseHeaders :: RespTx -> m (Either (LazyRespTx, HTTP.ResponseHeaders) RemoteField)
noResponseHeaders rTx = pure $ Left (liftTx rTx, [])

actionExecContext = ActionExecContext manager reqHeaders usrVars
userSession = _uiSession userInfo
actionExecContext = ActionExecContext manager reqHeaders $ _uiSession userInfo

convertMutationSelectionSet
:: (HasVersion, MonadIO m, MonadError QErr m)
=> GQLContext
-> SessionVariables
-> UserInfo
-> HTTP.Manager
-> HTTP.RequestHeaders
-> G.SelectionSet G.NoFragments G.Name
-> [G.VariableDefinition]
-> Maybe GH.VariableValues
-> m (ExecutionPlan (LazyRespTx, HTTP.ResponseHeaders) RemoteCall (G.Name, J.Value))
convertMutationSelectionSet gqlContext usrVars manager reqHeaders fields varDefs varValsM = do
convertMutationSelectionSet gqlContext userInfo manager reqHeaders fields varDefs varValsM = do
-- Parse the GraphQL query into the RQL AST
(unpreparedQueries, _reusability)
:: (OMap.InsOrdHashMap G.Name (MutationRootField UnpreparedValue), QueryReusability)
Expand All @@ -106,7 +107,7 @@ convertMutationSelectionSet gqlContext usrVars manager reqHeaders fields varDefs

-- Transform the RQL AST into a prepared SQL query
-- TODO pass the correct stringifyNum somewhere rather than True
txs <- for unpreparedQueries $ convertMutationRootField usrVars manager reqHeaders True
txs <- for unpreparedQueries $ convertMutationRootField userInfo manager reqHeaders True
let txList = OMap.toList txs
case (mapMaybe takeTx txList, mapMaybe takeRemote txList) of
(dbPlans, []) -> do
Expand Down Expand Up @@ -148,9 +149,9 @@ convertMutationSelectionSet gqlContext usrVars manager reqHeaders fields varDefs
:: (G.Name, Either (LazyRespTx, HTTP.ResponseHeaders) RemoteField)
-> Maybe (G.Name, (LazyRespTx, HTTP.ResponseHeaders))
takeTx (name, Left tx) = Just (name, tx)
takeTx _ = Nothing
takeTx _ = Nothing
takeRemote
:: (G.Name, Either (LazyRespTx, HTTP.ResponseHeaders) RemoteField)
-> Maybe (G.Name, RemoteField)
takeRemote (name, Right remote) = Just (name, remote)
takeRemote _ = Nothing
takeRemote _ = Nothing
17 changes: 10 additions & 7 deletions server/src-lib/Hasura/GraphQL/Execute/Plan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import qualified Data.Aeson.TH as J
import qualified Hasura.Cache as Cache
import qualified Hasura.GraphQL.Execute.LiveQuery as LQ
import qualified Hasura.GraphQL.Execute.Query as EQ
import qualified Hasura.GraphQL.Execute.Types as ET
import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
import Hasura.RQL.Types
import Hasura.Session
Expand All @@ -29,17 +30,19 @@ data PlanId
, _piRole :: !RoleName
, _piOperationName :: !(Maybe GH.OperationName)
, _piQuery :: !GH.GQLQueryText
, _piQueryType :: !ET.GraphQLQueryType
} deriving (Show, Eq, Ord, Generic)

instance Hashable PlanId

instance J.ToJSON PlanId where
toJSON (PlanId scVer rn opNameM query) =
toJSON (PlanId scVer rn opNameM query queryType) =
J.object
[ "schema_cache_version" J..= scVer
, "role" J..= rn
, "operation" J..= opNameM
, "query" J..= query
, "query_type" J..= queryType
]

newtype PlanCache
Expand Down Expand Up @@ -68,19 +71,19 @@ initPlanCache options =

getPlan
:: SchemaCacheVer -> RoleName -> Maybe GH.OperationName -> GH.GQLQueryText
-> PlanCache -> IO (Maybe ReusablePlan)
getPlan schemaVer rn opNameM q (PlanCache planCache) =
-> ET.GraphQLQueryType -> PlanCache -> IO (Maybe ReusablePlan)
getPlan schemaVer rn opNameM q queryType (PlanCache planCache) =
Cache.lookup planId planCache
where
planId = PlanId schemaVer rn opNameM q
planId = PlanId schemaVer rn opNameM q queryType

addPlan
:: SchemaCacheVer -> RoleName -> Maybe GH.OperationName -> GH.GQLQueryText
-> ReusablePlan -> PlanCache -> IO ()
addPlan schemaVer rn opNameM q queryPlan (PlanCache planCache) =
-> ReusablePlan -> ET.GraphQLQueryType -> PlanCache -> IO ()
addPlan schemaVer rn opNameM q queryPlan queryType (PlanCache planCache) =
Cache.insert planId queryPlan planCache
where
planId = PlanId schemaVer rn opNameM q
planId = PlanId schemaVer rn opNameM q queryType

clearPlanCache :: PlanCache -> IO ()
clearPlanCache (PlanCache planCache) =
Expand Down
Loading

0 comments on commit ab65b39

Please sign in to comment.