mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-21 06:21:39 +03:00
23e1cb218a
This PR simplifies the types that represent a remote relationship in IR so that they can be reused in other parts (in remote schema types) which could have remote relationships. The comments on the PR explain the main changes. PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2979 GitOrigin-RevId: 559c51d9d6ae79e2183ce4347018741b9096ac74
227 lines
8.2 KiB
Haskell
227 lines
8.2 KiB
Haskell
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
|
|
module Hasura.Backends.BigQuery.Instances.Execute () where
|
|
|
|
import Data.Aeson qualified as Aeson
|
|
import Data.Aeson.Text qualified as Aeson
|
|
import Data.HashMap.Strict qualified as Map
|
|
import Data.HashMap.Strict.InsOrd qualified as OMap
|
|
import Data.Text qualified as T
|
|
import Data.Text.Lazy qualified as LT
|
|
import Data.Text.Lazy.Builder qualified as LT
|
|
import Data.Vector qualified as V
|
|
import Hasura.Backends.BigQuery.Execute qualified as DataLoader
|
|
import Hasura.Backends.BigQuery.FromIr qualified as BigQuery
|
|
import Hasura.Backends.BigQuery.Plan
|
|
import Hasura.Backends.BigQuery.ToQuery qualified as ToQuery
|
|
import Hasura.Backends.BigQuery.Types qualified as BigQuery
|
|
import Hasura.Base.Error
|
|
import Hasura.Base.Error qualified as E
|
|
import Hasura.EncJSON
|
|
import Hasura.GraphQL.Execute.Backend
|
|
import Hasura.GraphQL.Namespace (RootFieldAlias)
|
|
import Hasura.GraphQL.Parser
|
|
import Hasura.Prelude
|
|
import Hasura.QueryTags
|
|
( emptyQueryTagsComment,
|
|
)
|
|
import Hasura.RQL.IR
|
|
import Hasura.RQL.IR.Select qualified as IR
|
|
import Hasura.RQL.Types
|
|
import Hasura.SQL.AnyBackend qualified as AB
|
|
import Hasura.Session
|
|
import Hasura.Tracing qualified as Tracing
|
|
|
|
instance BackendExecute 'BigQuery where
|
|
type PreparedQuery 'BigQuery = Text
|
|
type MultiplexedQuery 'BigQuery = Void
|
|
type ExecutionMonad 'BigQuery = Tracing.TraceT (ExceptT QErr IO)
|
|
|
|
mkDBQueryPlan = bqDBQueryPlan
|
|
mkDBMutationPlan = bqDBMutationPlan
|
|
mkDBSubscriptionPlan _ _ _ _ _ =
|
|
throw500 "Cannot currently perform subscriptions on BigQuery sources."
|
|
mkDBQueryExplain = bqDBQueryExplain
|
|
mkLiveQueryExplain _ =
|
|
throw500 "Cannot currently retrieve query execution plans on BigQuery sources."
|
|
|
|
-- NOTE: Currently unimplemented!.
|
|
--
|
|
-- This function is just a stub for future implementation; for now it just
|
|
-- throws a 500 error.
|
|
mkDBRemoteRelationshipPlan =
|
|
bqDBRemoteRelationshipPlan
|
|
|
|
-- query
|
|
|
|
bqDBQueryPlan ::
|
|
forall m.
|
|
( MonadError E.QErr m
|
|
) =>
|
|
UserInfo ->
|
|
SourceName ->
|
|
SourceConfig 'BigQuery ->
|
|
QueryDB 'BigQuery Void (UnpreparedValue 'BigQuery) ->
|
|
m (DBStepInfo 'BigQuery)
|
|
bqDBQueryPlan userInfo sourceName sourceConfig qrf = do
|
|
-- TODO (naveen): Append query tags to the query
|
|
select <- planNoPlan (BigQuery.bigQuerySourceConfigToFromIrConfig sourceConfig) userInfo qrf
|
|
let action = do
|
|
result <-
|
|
DataLoader.runExecute
|
|
sourceConfig
|
|
(DataLoader.executeSelect select)
|
|
case result of
|
|
Left err -> throw500WithDetail "dataLoader error" $ Aeson.toJSON $ show err
|
|
Right recordSet -> pure $! recordSetToEncJSON (BigQuery.selectCardinality select) recordSet
|
|
pure $ DBStepInfo @'BigQuery sourceName sourceConfig (Just (selectSQLTextForExplain select)) action
|
|
|
|
-- | Convert the dataloader's 'RecordSet' type to JSON.
|
|
recordSetToEncJSON :: BigQuery.Cardinality -> DataLoader.RecordSet -> EncJSON
|
|
recordSetToEncJSON cardinality DataLoader.RecordSet {rows} =
|
|
case cardinality of
|
|
BigQuery.One
|
|
| Just row <- rows V.!? 0 -> encJFromRecord row
|
|
| otherwise -> encJFromList (toList (fmap encJFromRecord rows))
|
|
BigQuery.Many -> encJFromList (toList (fmap encJFromRecord rows))
|
|
where
|
|
encJFromRecord =
|
|
encJFromInsOrdHashMap . fmap encJFromOutputValue . OMap.mapKeys coerce
|
|
encJFromOutputValue outputValue =
|
|
case outputValue of
|
|
DataLoader.NullOutputValue -> encJFromJValue Aeson.Null
|
|
DataLoader.DecimalOutputValue !i -> encJFromJValue i
|
|
DataLoader.BigDecimalOutputValue !i -> encJFromJValue i
|
|
DataLoader.FloatOutputValue !i -> encJFromJValue i
|
|
DataLoader.TextOutputValue !i -> encJFromJValue i
|
|
DataLoader.BytesOutputValue !i -> encJFromJValue i
|
|
DataLoader.DateOutputValue !i -> encJFromJValue i
|
|
DataLoader.TimestampOutputValue !i -> encJFromJValue i
|
|
DataLoader.TimeOutputValue !i -> encJFromJValue i
|
|
DataLoader.DatetimeOutputValue !i -> encJFromJValue i
|
|
DataLoader.GeographyOutputValue !i -> encJFromJValue i
|
|
DataLoader.BoolOutputValue !i -> encJFromJValue i
|
|
DataLoader.IntegerOutputValue !i -> encJFromJValue i
|
|
DataLoader.ArrayOutputValue !vector ->
|
|
encJFromList (toList (fmap encJFromOutputValue vector))
|
|
-- Really, the case below shouldn't be happening. But if it
|
|
-- does, it's not a problem either. The output will just have
|
|
-- a record in it.
|
|
DataLoader.RecordOutputValue !record -> encJFromRecord record
|
|
|
|
-- mutation
|
|
|
|
bqDBMutationPlan ::
|
|
forall m.
|
|
( MonadError E.QErr m
|
|
) =>
|
|
UserInfo ->
|
|
Bool ->
|
|
SourceName ->
|
|
SourceConfig 'BigQuery ->
|
|
MutationDB 'BigQuery Void (UnpreparedValue 'BigQuery) ->
|
|
m (DBStepInfo 'BigQuery)
|
|
bqDBMutationPlan _userInfo _stringifyNum _sourceName _sourceConfig _mrf =
|
|
throw500 "mutations are not supported in BigQuery; this should be unreachable"
|
|
|
|
-- explain
|
|
|
|
bqDBQueryExplain ::
|
|
MonadError E.QErr m =>
|
|
RootFieldAlias ->
|
|
UserInfo ->
|
|
SourceName ->
|
|
SourceConfig 'BigQuery ->
|
|
QueryDB 'BigQuery Void (UnpreparedValue 'BigQuery) ->
|
|
m (AB.AnyBackend DBStepInfo)
|
|
bqDBQueryExplain fieldName userInfo sourceName sourceConfig qrf = do
|
|
select <- planNoPlan (BigQuery.bigQuerySourceConfigToFromIrConfig sourceConfig) userInfo qrf
|
|
let textSQL = selectSQLTextForExplain select
|
|
pure $
|
|
AB.mkAnyBackend $
|
|
DBStepInfo @'BigQuery sourceName sourceConfig Nothing $
|
|
pure $
|
|
encJFromJValue $
|
|
ExplainPlan
|
|
fieldName
|
|
(Just $ textSQL)
|
|
(Just $ T.lines $ textSQL)
|
|
|
|
-- | Get the SQL text for a select, with parameters left as $1, $2, .. holes.
|
|
selectSQLTextForExplain :: BigQuery.Select -> Text
|
|
selectSQLTextForExplain =
|
|
LT.toStrict
|
|
. LT.toLazyText
|
|
. fst
|
|
. ToQuery.renderBuilderPretty
|
|
. ToQuery.fromSelect
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Remote Relationships (e.g. DB-to-DB Joins, remote schema joins, etc.)
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Construct an action (i.e. 'DBStepInfo') which can marshal some remote
|
|
-- relationship information into a form that BigQuery can query against.
|
|
--
|
|
-- XXX: Currently unimplemented; the Postgres implementation uses
|
|
-- @jsonb_to_recordset@ to query the remote relationship, however this
|
|
-- functionality doesn't exist in BigQuery.
|
|
--
|
|
-- NOTE: The following typeclass constraints will be necessary when implementing
|
|
-- this function for real:
|
|
--
|
|
-- @
|
|
-- MonadQueryTags m
|
|
-- Backend 'BigQuery
|
|
-- @
|
|
bqDBRemoteRelationshipPlan ::
|
|
forall m.
|
|
( MonadError QErr m
|
|
) =>
|
|
UserInfo ->
|
|
SourceName ->
|
|
SourceConfig 'BigQuery ->
|
|
-- | List of json objects, each of which becomes a row of the table.
|
|
NonEmpty Aeson.Object ->
|
|
-- | The above objects have this schema
|
|
--
|
|
-- XXX: What is this for/what does this mean?
|
|
HashMap FieldName (Column 'BigQuery, ScalarType 'BigQuery) ->
|
|
-- | This is a field name from the lhs that *has* to be selected in the
|
|
-- response along with the relationship.
|
|
FieldName ->
|
|
(FieldName, SourceRelationshipSelection 'BigQuery Void UnpreparedValue) ->
|
|
m (DBStepInfo 'BigQuery)
|
|
bqDBRemoteRelationshipPlan userInfo sourceName sourceConfig lhs lhsSchema argumentId relationship = do
|
|
flip runReaderT emptyQueryTagsComment $ bqDBQueryPlan userInfo sourceName sourceConfig rootSelection
|
|
where
|
|
coerceToColumn = BigQuery.ColumnName . getFieldNameTxt
|
|
joinColumnMapping = mapKeys coerceToColumn lhsSchema
|
|
|
|
rowsArgument :: UnpreparedValue 'BigQuery
|
|
rowsArgument =
|
|
UVParameter Nothing $
|
|
ColumnValue (ColumnScalar BigQuery.StringScalarType) $
|
|
BigQuery.StringValue . LT.toStrict $ Aeson.encodeToLazyText lhs
|
|
|
|
recordSetDefinitionList =
|
|
(coerceToColumn argumentId, BigQuery.IntegerScalarType) : Map.toList (fmap snd joinColumnMapping)
|
|
|
|
jsonToRecordSet :: IR.SelectFromG ('BigQuery) (UnpreparedValue 'BigQuery)
|
|
jsonToRecordSet =
|
|
IR.FromFunction
|
|
(BigQuery.FunctionName "unnest")
|
|
( IR.FunctionArgsExp
|
|
[IR.AEInput rowsArgument]
|
|
mempty
|
|
)
|
|
(Just recordSetDefinitionList)
|
|
|
|
rootSelection =
|
|
convertRemoteSourceRelationship
|
|
(fst <$> joinColumnMapping)
|
|
jsonToRecordSet
|
|
(BigQuery.ColumnName $ getFieldNameTxt argumentId)
|
|
(ColumnScalar BigQuery.IntegerScalarType)
|
|
relationship
|