graphql-engine/server/src-lib/Hasura/Backends/BigQuery/Instances/Execute.hs
Rakesh Emmadi f2a5d7cef3 server/pro/multitenant: Postgres connection routing using kriti templates
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6822
Co-authored-by: paritosh-08 <85472423+paritosh-08@users.noreply.github.com>
Co-authored-by: Naveen Naidu <30195193+Naveenaidu@users.noreply.github.com>
Co-authored-by: Sooraj <8408875+soorajshankar@users.noreply.github.com>
Co-authored-by: Varun Choudhary <68095256+Varun-Choudhary@users.noreply.github.com>
Co-authored-by: Sean Park-Ross <94021366+seanparkross@users.noreply.github.com>
GitOrigin-RevId: 61cfc00a97de88df1ede3f26829a0d78ec9c0bc5
2023-01-25 07:14:31 +00:00

254 lines
9.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.Environment qualified as Env
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.Schema.Options qualified as Options
import Hasura.Prelude
import Hasura.QueryTags
( emptyQueryTagsComment,
)
import Hasura.RQL.IR
import Hasura.RQL.IR.Select qualified as IR
import Hasura.RQL.Types.Backend
import Hasura.RQL.Types.Column
import Hasura.RQL.Types.Common
import Hasura.RQL.Types.Function
import Hasura.SQL.AnyBackend qualified as AB
import Hasura.SQL.Backend
import Hasura.Session
import Hasura.Tracing qualified as Tracing
import Language.GraphQL.Draft.Syntax qualified as G
import Network.HTTP.Types qualified as HTTP
instance BackendExecute 'BigQuery where
type PreparedQuery 'BigQuery = Text
type MultiplexedQuery 'BigQuery = Void
type ExecutionMonad 'BigQuery = Tracing.TraceT (ExceptT QErr IO)
mkDBQueryPlan = bqDBQueryPlan
mkDBMutationPlan = bqDBMutationPlan
mkLiveQuerySubscriptionPlan _ _ _ _ _ _ _ =
throw500 "Cannot currently perform subscriptions on BigQuery sources."
mkDBStreamingSubscriptionPlan _ _ _ _ _ _ =
throw500 "Cannot currently perform subscriptions on BigQuery sources."
mkDBQueryExplain = bqDBQueryExplain
mkSubscriptionExplain _ =
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 ->
Env.Environment ->
SourceName ->
SourceConfig 'BigQuery ->
QueryDB 'BigQuery Void (UnpreparedValue 'BigQuery) ->
[HTTP.Header] ->
Maybe G.Name ->
m (DBStepInfo 'BigQuery)
bqDBQueryPlan userInfo _env 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.executeProblemMessage DataLoader.HideDetails err) $ Aeson.toJSON 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 ->
Env.Environment ->
Options.StringifyNumbers ->
SourceName ->
SourceConfig 'BigQuery ->
MutationDB 'BigQuery Void (UnpreparedValue 'BigQuery) ->
[HTTP.Header] ->
Maybe G.Name ->
m (DBStepInfo 'BigQuery)
bqDBMutationPlan _userInfo _environment _stringifyNum _sourceName _sourceConfig _mrf _headers _gName =
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) ->
[HTTP.Header] ->
Maybe G.Name ->
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) ->
[HTTP.Header] ->
Maybe G.Name ->
Options.StringifyNumbers ->
m (DBStepInfo 'BigQuery)
bqDBRemoteRelationshipPlan userInfo sourceName sourceConfig lhs lhsSchema argumentId relationship reqHeaders operationName stringifyNumbers = do
flip runReaderT emptyQueryTagsComment $ bqDBQueryPlan userInfo Env.emptyEnvironment sourceName sourceConfig rootSelection reqHeaders operationName
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" Nothing)
( FunctionArgsExp
[BigQuery.AEInput rowsArgument]
mempty
)
(Just recordSetDefinitionList)
rootSelection =
convertRemoteSourceRelationship
(fst <$> joinColumnMapping)
jsonToRecordSet
(BigQuery.ColumnName $ getFieldNameTxt argumentId)
(ColumnScalar BigQuery.IntegerScalarType)
relationship
stringifyNumbers