mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 12:31:52 +03:00
15ed0cf536
GitOrigin-RevId: f142908fa3dd3b0cb8887521c639be4a017e606a
251 lines
10 KiB
Haskell
251 lines
10 KiB
Haskell
module Hasura.GraphQL.Execute.RemoteJoin
|
|
( RemoteJoins
|
|
, RemoteJoinMap
|
|
, FieldPath(..)
|
|
, appendPath
|
|
, getRemoteJoins
|
|
, getRemoteJoinsSelect
|
|
, getRemoteJoinsAggregateSelect
|
|
, getRemoteJoinsConnectionSelect
|
|
, getRemoteJoinsMutationOutput
|
|
) where
|
|
|
|
import Hasura.Prelude
|
|
|
|
import qualified Data.HashMap.Strict as Map
|
|
import qualified Data.HashSet as HS
|
|
import qualified Data.List.NonEmpty as NE
|
|
|
|
import Control.Lens
|
|
|
|
import Hasura.GraphQL.Context
|
|
import Hasura.RQL.IR.RemoteJoin
|
|
import Hasura.RQL.IR.Returning
|
|
import Hasura.RQL.IR.Select
|
|
import Hasura.RQL.Types
|
|
|
|
{- Note: [Remote Joins Architecture]
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Unparsed Incoming GraphQL +------------------------------+
|
|
--------------------------> | Parsing of the GraphQL query |-----+
|
|
+------------------------------+ |
|
|
| DB Query and remote joins (if any)
|
|
|
|
|
V
|
|
+----------------------------------+ SQL query response +----------------------------+
|
|
| Traverse the DB response to | <------------------- | Execution of the DB query |
|
|
| get the values of the arguments | +----------------------------+
|
|
| of the remote field |
|
|
+----------------------------------+
|
|
|
|
|
| Remote field arguments
|
|
V
|
|
+--------------------------+ Remote schema response +----------------------------------------+
|
|
| Query the remote schema | ------------------------> | Replace the remote join fields in |
|
|
| with the remote field | | the SQL query response (JSON) with |
|
|
| arguments to the remote | | the response obtained from the remote |
|
|
| field configured in the | | schema at appropriate places. |
|
|
| remote join. | +----------------------------------------+
|
|
+--------------------------+
|
|
-}
|
|
|
|
|
|
|
|
-- remote joins info
|
|
|
|
-- | Path to the remote join field in query response JSON from Postgres.
|
|
newtype FieldPath = FieldPath {unFieldPath :: [FieldName]}
|
|
deriving (Show, Eq, Semigroup, Monoid, Hashable)
|
|
|
|
appendPath :: FieldName -> FieldPath -> FieldPath
|
|
appendPath fieldName = FieldPath . (<> [fieldName]) . unFieldPath
|
|
|
|
|
|
type RemoteJoins b = NE.NonEmpty (FieldPath, NE.NonEmpty (RemoteJoin b))
|
|
type RemoteJoinMap b = Map.HashMap FieldPath (NE.NonEmpty (RemoteJoin b))
|
|
|
|
|
|
-- extract remote joins out of a root field
|
|
|
|
getRemoteJoins
|
|
:: Backend b
|
|
=> QueryDB b u
|
|
-> Maybe (RemoteJoins b)
|
|
getRemoteJoins = \case
|
|
QDBMultipleRows s -> snd $ getRemoteJoinsSelect s
|
|
QDBSingleRow s -> snd $ getRemoteJoinsSelect s
|
|
QDBAggregation s -> snd $ getRemoteJoinsAggregateSelect s
|
|
QDBConnection s -> snd $ getRemoteJoinsConnectionSelect s
|
|
|
|
-- | Traverse through 'AnnSimpleSel' and collect remote join fields (if any).
|
|
getRemoteJoinsSelect
|
|
:: Backend b
|
|
=> AnnSimpleSelG b u
|
|
-> (AnnSimpleSelG b u, Maybe (RemoteJoins b))
|
|
getRemoteJoinsSelect =
|
|
second mapToNonEmpty . flip runState mempty . transformSelect mempty
|
|
|
|
-- | Traverse through @'AnnAggregateSelect' and collect remote join fields (if any).
|
|
getRemoteJoinsAggregateSelect
|
|
:: Backend b
|
|
=> AnnAggregateSelectG b u
|
|
-> (AnnAggregateSelectG b u, Maybe (RemoteJoins b))
|
|
getRemoteJoinsAggregateSelect =
|
|
second mapToNonEmpty . flip runState mempty . transformAggregateSelect mempty
|
|
|
|
-- | Traverse through @'ConnectionSelect' and collect remote join fields (if any).
|
|
getRemoteJoinsConnectionSelect
|
|
:: Backend b
|
|
=> ConnectionSelect b u
|
|
-> (ConnectionSelect b u, Maybe (RemoteJoins b))
|
|
getRemoteJoinsConnectionSelect =
|
|
second mapToNonEmpty . flip runState mempty . transformConnectionSelect mempty
|
|
|
|
-- | Traverse through 'MutationOutput' and collect remote join fields (if any)
|
|
getRemoteJoinsMutationOutput
|
|
:: Backend b
|
|
=> MutationOutputG b u
|
|
-> (MutationOutputG b u, Maybe (RemoteJoins b))
|
|
getRemoteJoinsMutationOutput =
|
|
second mapToNonEmpty . flip runState mempty . transformMutationOutput mempty
|
|
where
|
|
transformMutationOutput path = \case
|
|
MOutMultirowFields mutationFields ->
|
|
MOutMultirowFields <$> transfromMutationFields mutationFields
|
|
MOutSinglerowObject annFields ->
|
|
MOutSinglerowObject <$> transformAnnFields path annFields
|
|
where
|
|
transfromMutationFields fields =
|
|
forM fields $ \(fieldName, field') -> do
|
|
let fieldPath = appendPath fieldName path
|
|
(fieldName,) <$> case field' of
|
|
MCount -> pure MCount
|
|
MExp t -> pure $ MExp t
|
|
MRet annFields -> MRet <$> transformAnnFields fieldPath annFields
|
|
|
|
|
|
-- local helpers
|
|
|
|
transformSelect
|
|
:: Backend b
|
|
=> FieldPath
|
|
-> AnnSimpleSelG b u
|
|
-> State (RemoteJoinMap b) (AnnSimpleSelG b u)
|
|
transformSelect path sel = do
|
|
let fields = _asnFields sel
|
|
-- Transform selects in array, object and computed fields
|
|
transformedFields <- transformAnnFields path fields
|
|
pure sel{_asnFields = transformedFields}
|
|
|
|
transformAggregateSelect
|
|
:: Backend b
|
|
=> FieldPath
|
|
-> AnnAggregateSelectG b u
|
|
-> State (RemoteJoinMap b) (AnnAggregateSelectG b u)
|
|
transformAggregateSelect path sel = do
|
|
let aggFields = _asnFields sel
|
|
transformedFields <- forM aggFields $ \(fieldName, aggField) ->
|
|
(fieldName,) <$> case aggField of
|
|
TAFAgg agg -> pure $ TAFAgg agg
|
|
TAFNodes x annFields -> TAFNodes x <$> transformAnnFields (appendPath fieldName path) annFields
|
|
TAFExp t -> pure $ TAFExp t
|
|
pure sel{_asnFields = transformedFields}
|
|
|
|
transformConnectionSelect
|
|
:: Backend b
|
|
=> FieldPath
|
|
-> ConnectionSelect b u
|
|
-> State (RemoteJoinMap b) (ConnectionSelect b u)
|
|
transformConnectionSelect path ConnectionSelect{..} = do
|
|
let connectionFields = _asnFields _csSelect
|
|
transformedFields <- forM connectionFields $ \(fieldName, field) ->
|
|
(fieldName,) <$> case field of
|
|
ConnectionTypename t -> pure $ ConnectionTypename t
|
|
ConnectionPageInfo p -> pure $ ConnectionPageInfo p
|
|
ConnectionEdges edges -> ConnectionEdges <$> transformEdges (appendPath fieldName path) edges
|
|
let select = _csSelect{_asnFields = transformedFields}
|
|
pure $ ConnectionSelect _csXRelay _csPrimaryKeyColumns _csSplit _csSlice select
|
|
where
|
|
transformEdges edgePath edgeFields =
|
|
forM edgeFields $ \(fieldName, edgeField) ->
|
|
(fieldName,) <$> case edgeField of
|
|
EdgeTypename t -> pure $ EdgeTypename t
|
|
EdgeCursor -> pure EdgeCursor
|
|
EdgeNode annFields ->
|
|
EdgeNode <$> transformAnnFields (appendPath fieldName edgePath) annFields
|
|
|
|
transformObjectSelect
|
|
:: Backend b
|
|
=> FieldPath
|
|
-> AnnObjectSelectG b u
|
|
-> State (RemoteJoinMap b) (AnnObjectSelectG b u)
|
|
transformObjectSelect path sel = do
|
|
let fields = _aosFields sel
|
|
transformedFields <- transformAnnFields path fields
|
|
pure sel{_aosFields = transformedFields}
|
|
|
|
transformAnnFields
|
|
:: forall b u
|
|
. Backend b
|
|
=> FieldPath
|
|
-> AnnFieldsG b u
|
|
-> State (RemoteJoinMap b) (AnnFieldsG b u)
|
|
transformAnnFields path fields = do
|
|
let pgColumnFields = map fst $ getFields _AFColumn fields
|
|
remoteSelects = getFields (_AFRemote . _2) fields
|
|
remoteJoins = flip map remoteSelects $ \(fieldName, remoteSelect) ->
|
|
let RemoteSelect argsMap selSet hasuraColumns remoteFields rsi = remoteSelect
|
|
hasuraColumnL = toList hasuraColumns
|
|
hasuraColumnFields = HS.fromList $ map (fromCol @b . pgiColumn) hasuraColumnL
|
|
phantomColumns = filter ((`notElem` pgColumnFields) . fromCol @b . pgiColumn) hasuraColumnL
|
|
in RemoteJoin fieldName argsMap selSet hasuraColumnFields remoteFields rsi phantomColumns
|
|
|
|
transformedFields <- forM fields $ \(fieldName, field') -> do
|
|
let fieldPath = appendPath fieldName path
|
|
(fieldName,) <$> case field' of
|
|
AFNodeId x qt pkeys -> pure $ AFNodeId x qt pkeys
|
|
AFColumn c -> pure $ AFColumn c
|
|
AFObjectRelation annRel ->
|
|
AFObjectRelation <$> transformAnnRelation annRel (transformObjectSelect fieldPath)
|
|
AFArrayRelation (ASSimple annRel) ->
|
|
AFArrayRelation . ASSimple <$> transformAnnRelation annRel (transformSelect fieldPath)
|
|
AFArrayRelation (ASAggregate aggRel) ->
|
|
AFArrayRelation . ASAggregate <$> transformAnnAggregateRelation fieldPath aggRel
|
|
AFArrayRelation (ASConnection annRel) ->
|
|
AFArrayRelation . ASConnection <$> transformArrayConnection fieldPath annRel
|
|
AFComputedField x computedField ->
|
|
AFComputedField x <$> case computedField of
|
|
CFSScalar _ -> pure computedField
|
|
CFSTable jas annSel -> CFSTable jas <$> transformSelect fieldPath annSel
|
|
AFRemote x rs -> pure $ AFRemote x rs
|
|
AFExpression t -> pure $ AFExpression t
|
|
|
|
case NE.nonEmpty remoteJoins of
|
|
Nothing -> pure transformedFields
|
|
Just nonEmptyRemoteJoins -> do
|
|
let phantomColumns = map (\ci -> (fromCol @b $ pgiColumn ci, AFColumn $ AnnColumnField ci False Nothing)) $
|
|
concatMap _rjPhantomFields remoteJoins
|
|
modify (Map.insert path nonEmptyRemoteJoins)
|
|
pure $ transformedFields <> phantomColumns
|
|
where
|
|
getFields f = mapMaybe (sequence . second (^? f))
|
|
|
|
transformAnnRelation annRel f = do
|
|
let annSel = aarAnnSelect annRel
|
|
transformedSel <- f annSel
|
|
pure annRel{aarAnnSelect = transformedSel}
|
|
|
|
transformAnnAggregateRelation fieldPath annRel = do
|
|
let annSel = aarAnnSelect annRel
|
|
transformedSel <- transformAggregateSelect fieldPath annSel
|
|
pure annRel{aarAnnSelect = transformedSel}
|
|
|
|
transformArrayConnection fieldPath annRel = do
|
|
let connectionSelect = aarAnnSelect annRel
|
|
transformedConnectionSelect <- transformConnectionSelect fieldPath connectionSelect
|
|
pure annRel{aarAnnSelect = transformedConnectionSelect}
|
|
|
|
mapToNonEmpty :: RemoteJoinMap backend -> Maybe (RemoteJoins backend)
|
|
mapToNonEmpty = NE.nonEmpty . Map.toList
|