Implement Schema Parsers for Native Query Interface

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7574
GitOrigin-RevId: 0cb4638a7dd79abf6ccb05092c0c663c84675bbd
This commit is contained in:
Philip Lykke Carlsen 2023-01-19 12:25:52 +01:00 committed by hasura-bot
parent 094b5e6db2
commit cd5186be90
15 changed files with 339 additions and 9 deletions

View File

@ -713,6 +713,10 @@ library
, Hasura.Incremental.Internal.Cache
, Hasura.Incremental.Internal.Dependency
, Hasura.Incremental.Internal.Rule
, Hasura.NativeQuery.IR
, Hasura.NativeQuery.Metadata
, Hasura.NativeQuery.Schema
, Hasura.NativeQuery.Types
, Hasura.Server.Auth.WebHook
, Hasura.Server.Middleware
, Hasura.Server.Cors
@ -848,13 +852,13 @@ library
, Hasura.RQL.IR.Delete
, Hasura.RQL.IR.Insert
, Hasura.RQL.IR.OrderBy
, Hasura.RQL.IR.Returning
, Hasura.RQL.IR.Select
, Hasura.RQL.IR.RemoteSchema
, Hasura.RQL.IR.Returning
, Hasura.RQL.IR.Root
, Hasura.RQL.IR.Select
, Hasura.RQL.IR.Update
, Hasura.RQL.IR.Update.Batch
, Hasura.RQL.IR.Value
, Hasura.RQL.IR.Root
, Hasura.RQL.IR
, Hasura.GraphQL.Analyse
, Hasura.GraphQL.ApolloFederation

View File

@ -66,6 +66,7 @@ import Hasura.GraphQL.Schema.Select
import Hasura.GraphQL.Schema.Update qualified as SU
import Hasura.GraphQL.Schema.Update.Batch qualified as SUB
import Hasura.Name qualified as Name
import Hasura.NativeQuery.Schema qualified as NativeQueries
import Hasura.Prelude
import Hasura.RQL.IR.BoolExp
import Hasura.RQL.IR.Root (RemoteRelationshipField)
@ -297,6 +298,7 @@ instance
buildFunctionQueryFields = buildFunctionQueryFieldsPG
buildFunctionRelayQueryFields = pgkBuildFunctionRelayQueryFields
buildFunctionMutationFields = buildFunctionMutationFieldsPG
buildNativeQueryRootFields = NativeQueries.defaultBuildNativeQueryRootFields
mkRelationshipParser = GSB.mkDefaultRelationshipParser backendInsertParser ()

View File

@ -27,6 +27,8 @@ import Hasura.Backends.Postgres.Types.Function qualified as Postgres
import Hasura.Backends.Postgres.Types.Insert qualified as Postgres (BackendInsert)
import Hasura.Backends.Postgres.Types.Update qualified as Postgres
import Hasura.Base.Error
import Hasura.NativeQuery.IR (NativeQueryImpl)
import Hasura.NativeQuery.Metadata
import Hasura.Prelude
import Hasura.RQL.IR.BoolExp.AggregationPredicates qualified as Agg
import Hasura.RQL.Types.Backend
@ -115,6 +117,9 @@ instance
type ExtraTableMetadata ('Postgres pgKind) = PgExtraTableMetadata pgKind
type BackendInsert ('Postgres pgKind) = Postgres.BackendInsert pgKind
type NativeQueryInfo ('Postgres pgKind) = NativeQueryInfoImpl ('Postgres pgKind)
type NativeQuery ('Postgres pgKind) = NativeQueryImpl ('Postgres pgKind)
type XComputedField ('Postgres pgKind) = XEnable
type XRelay ('Postgres pgKind) = XEnable
type XNodesAgg ('Postgres pgKind) = XEnable

View File

@ -114,6 +114,8 @@ selectFromToFromItem prefix = \case
S.mkFunctionAlias
qf
(fmap (fmap (first S.toColumnAlias)) defListM)
-- This behavior is hidden behind a flag, so cannot be triggered yet.
FromNativeQuery _ -> error "unimplemented"
-- | Converts a function name to an 'Identifier'.
--

View File

@ -165,6 +165,8 @@ processSelectParams
FromTable table -> S.QualTable table
FromIdentifier i -> S.QualifiedIdentifier (TableIdentifier $ unFIIdentifier i) Nothing
FromFunction qf _ _ -> S.QualifiedIdentifier (TableIdentifier $ qualifiedObjectToText qf) Nothing
-- This behavior is hidden behind a flag, so cannot be triggered yet.
FromNativeQuery _ -> error "unimplemented"
processAnnAggregateSelect ::
forall pgKind m.

View File

@ -3,6 +3,8 @@
-- | Types concerned with user-specified custom SQL fragments.
module Hasura.CustomSQL
( CustomSQLParameter (..),
CustomSQLParameterName (..),
CustomSQLParameterType (..),
)
where

View File

@ -332,15 +332,18 @@ buildRoleContext options sources remotes actions customTypes role remoteSchemaPe
runSourceSchema schemaContext schemaOptions sourceInfo do
let validFunctions = takeValidFunctions functions
validTables = takeValidTables tables
nativeQueries = _siNativeQueries _customSQL
mkRootFieldName = _rscRootFields sourceCustomization
makeTypename = SC._rscTypeNames sourceCustomization
(uncustomizedQueryRootFields, uncustomizedSubscriptionRootFields, apolloFedTableParsers) <-
buildQueryAndSubscriptionFields mkRootFieldName sourceInfo validTables validFunctions
(nativeQueryRootFields) <-
buildNativeQueryFields sourceInfo nativeQueries
(,,,,apolloFedTableParsers)
<$> customizeFields
sourceCustomization
(makeTypename <> MkTypename (<> Name.__query))
(pure uncustomizedQueryRootFields)
(pure (uncustomizedQueryRootFields <> nativeQueryRootFields))
<*> customizeFields
sourceCustomization
(makeTypename <> MkTypename (<> Name.__mutation_frontend))
@ -674,6 +677,33 @@ buildQueryAndSubscriptionFields mkRootFieldName sourceInfo tables (takeExposedAs
sourceConfig = _siConfiguration sourceInfo
queryTagsConfig = _siQueryTagsConfig sourceInfo
runMaybeTmempty :: (Monad m, Monoid a) => MaybeT m a -> m a
runMaybeTmempty = (`onNothingM` (pure mempty)) . runMaybeT
buildNativeQueryFields ::
forall b r m n.
MonadBuildSchema b r m n =>
SourceInfo b ->
NativeQueryCache b ->
SchemaT r m [P.FieldParser n (QueryRootField UnpreparedValue)]
buildNativeQueryFields sourceInfo nativeQueries = runMaybeTmempty $ do
roleName <- retrieve scRole
-- Native queries are only enabled for the admin role, pending the design of
-- permissions for native queries.
guard $ roleName == adminRoleName
map mkRF . catMaybes <$> for (Map.elems nativeQueries) \nativeQuery -> do
lift $ (buildNativeQueryRootFields nativeQuery)
where
mkRF ::
FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b)) ->
FieldParser n (QueryRootField UnpreparedValue)
mkRF = mkRootField sourceName sourceConfig queryTagsConfig QDBR
sourceName = _siName sourceInfo
sourceConfig = _siConfiguration sourceInfo
queryTagsConfig = _siQueryTagsConfig sourceInfo
buildRelayQueryAndSubscriptionFields ::
forall b r m n.
MonadBuildSchema b r m n =>

View File

@ -180,6 +180,15 @@ class
TableName b ->
SchemaT r m [FieldParser n (MutationDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
buildNativeQueryRootFields ::
MonadBuildSchema b r m n =>
NativeQueryInfo b ->
SchemaT
r
m
(Maybe (FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))))
buildNativeQueryRootFields _ = pure Nothing
-- | Make a parser for relationships. Default implementaton elides
-- relationships altogether.
mkRelationshipParser ::

View File

@ -0,0 +1,22 @@
-- | This module contains the default types and functions that model Native
-- Queries.
module Hasura.NativeQuery.IR
( NativeQueryImpl (..),
)
where
import Hasura.NativeQuery.Metadata
import Hasura.NativeQuery.Types
import Hasura.Prelude
import Hasura.RQL.Types.Column (ColumnValue)
-- | The default implementation of an invocation of a native query.
data NativeQueryImpl b field = NativeQueryImpl
{ -- | The defined name of the native query.
-- When translating this is used as a key to look up the actual
-- native query definition.
nqName :: NativeQueryName,
-- | The arguments passed to the native query, if any.
nqArgs :: HashMap NativeQueryArgumentName (ColumnValue b)
}
deriving (Eq, Functor, Foldable, Traversable, Show)

View File

@ -0,0 +1,23 @@
-- | This module houses the types and functions associated with the default
-- implementation of the metadata of native queries.
module Hasura.NativeQuery.Metadata
( NativeQueryArgumentName (..),
NativeQueryInfoImpl (..),
)
where
import Hasura.NativeQuery.Types
import Hasura.Prelude
import Hasura.RQL.Types.Backend
newtype NativeQueryArgumentName = NativeQueryArgumentName {getNativeQueryArgumentName :: Text}
deriving (Eq, Ord, Hashable, Show)
-- | Provisional data type for modelling metadata
data NativeQueryInfoImpl b = NativeQueryInfoImpl
{ nqiiName :: NativeQueryName,
nqiiCode :: Text,
nqiiReturns :: TableName b,
nqiiArgs :: HashMap NativeQueryArgumentName (ScalarType b),
nqiiComment :: Text
}

View File

@ -0,0 +1,132 @@
{-# LANGUAGE QuasiQuotes #-}
module Hasura.NativeQuery.Schema (defaultBuildNativeQueryRootFields) where
import Data.Has (Has (getter))
import Data.HashMap.Strict qualified as HM
import Data.Monoid (Ap (Ap, getAp))
import Hasura.GraphQL.Schema.Backend
( BackendSchema (columnParser),
BackendTableSelectSchema (tableArguments),
MonadBuildSchema,
)
import Hasura.GraphQL.Schema.Common
( SchemaContext (scRole),
SchemaT,
askTableInfo,
retrieve,
)
import Hasura.GraphQL.Schema.Options qualified as Options
import Hasura.GraphQL.Schema.Parser qualified as P
import Hasura.GraphQL.Schema.Select
( tablePermissionsInfo,
tableSelectionList,
)
import Hasura.GraphQL.Schema.Table (tableSelectPermissions)
import Hasura.NativeQuery.IR (NativeQueryImpl (..))
import Hasura.NativeQuery.Metadata
( NativeQueryArgumentName (..),
NativeQueryInfoImpl (..),
)
import Hasura.NativeQuery.Types (NativeQueryName (..))
import Hasura.Prelude
import Hasura.RQL.IR.Root (RemoteRelationshipField)
import Hasura.RQL.IR.Select (QueryDB (QDBSingleRow))
import Hasura.RQL.IR.Select qualified as IR
import Hasura.RQL.IR.Value (UnpreparedValue, openValueOrigin)
import Hasura.RQL.Types.Backend
( Backend (NativeQuery, ScalarType),
)
import Hasura.RQL.Types.Column qualified as Column
import Hasura.RQL.Types.Metadata.Object qualified as MO
import Hasura.RQL.Types.Source
( SourceInfo (_siCustomization, _siName),
)
import Hasura.RQL.Types.SourceCustomization
( ResolvedSourceCustomization (_rscNamingConvention),
)
import Hasura.RQL.Types.Table (tableInfoName)
import Hasura.SQL.AnyBackend (mkAnyBackend)
import Language.GraphQL.Draft.Syntax qualified as G
import Language.GraphQL.Draft.Syntax.QQ qualified as G
defaultBuildNativeQueryRootFields ::
forall b r m n.
( MonadBuildSchema b r m n,
BackendTableSelectSchema b,
NativeQuery b ~ NativeQueryImpl b
) =>
NativeQueryInfoImpl b ->
SchemaT
r
m
(Maybe (P.FieldParser n (QueryDB b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))))
defaultBuildNativeQueryRootFields NativeQueryInfoImpl {..} = runMaybeT $ do
tableInfo <- askTableInfo @b nqiiReturns
fieldName <- hoistMaybe (G.mkName $ getNativeQueryName nqiiName)
nativeQueryArgsParser <- nativeQueryArgumentsSchema @b @r @m @n fieldName nqiiArgs
sourceInfo :: SourceInfo b <- asks getter
let sourceName = _siName sourceInfo
tableName = tableInfoName tableInfo
tCase = _rscNamingConvention $ _siCustomization sourceInfo
description = Just $ G.Description $ "A native query called " <> getNativeQueryName nqiiName
stringifyNumbers <- retrieve Options.soStringifyNumbers
roleName <- retrieve scRole
selectionSetParser <- MaybeT $ tableSelectionList @b @r @m @n tableInfo
tableArgsParser <- lift $ tableArguments @b @r @m @n tableInfo
selectPermissions <- hoistMaybe $ tableSelectPermissions roleName tableInfo
pure $
P.setFieldParserOrigin (MO.MOSourceObjId sourceName (mkAnyBackend $ MO.SMOTable @b tableName)) $
P.subselection fieldName description ((,) <$> tableArgsParser <*> nativeQueryArgsParser) selectionSetParser
<&> \((args, nqArgs), fields) ->
QDBSingleRow $
IR.AnnSelectG
{ IR._asnFields = fields,
IR._asnFrom =
IR.FromNativeQuery
NativeQueryImpl
{ nqName = nqiiName,
nqArgs
},
IR._asnPerm = tablePermissionsInfo selectPermissions,
IR._asnArgs = args,
IR._asnStrfyNum = stringifyNumbers,
IR._asnNamingConvention = Just tCase
}
nativeQueryArgumentsSchema ::
forall b r m n.
MonadBuildSchema b r m n =>
G.Name ->
HashMap NativeQueryArgumentName (ScalarType b) ->
MaybeT (SchemaT r m) (P.InputFieldsParser n (HashMap NativeQueryArgumentName (Column.ColumnValue b)))
nativeQueryArgumentsSchema nativeQueryName argsSignature = do
-- Lift 'SchemaT r m (InputFieldsParser ..)' into a monoid using Applicative.
-- This lets us use 'foldMap' + monoid structure of hashmaps to avoid awkwardly
-- traversing the arguments and building the resulting parser.
argsParser <-
getAp $
foldMap
( \(name, ty) -> Ap do
argValueParser <-
fmap (HM.singleton name . openValueOrigin)
<$> lift (columnParser (Column.ColumnScalar ty) (G.Nullability False))
-- TODO: Break in some interesting way if we cannot make a name?
-- TODO: Naming conventions?
-- TODO: Custom fields? (Probably not)
argName <- hoistMaybe (G.mkName (getNativeQueryArgumentName name))
return $
P.field
argName
(Just $ G.Description ("Native query argument " <> getNativeQueryArgumentName name))
argValueParser
)
(HM.toList argsSignature)
let desc = Just $ G.Description $ G.unName nativeQueryName <> " Native Query Arguments"
pure $
P.field
[G.name|args|]
desc
(P.object (nativeQueryName <> [G.name|_arguments|]) desc argsParser)

View File

@ -0,0 +1,11 @@
-- | This module houses the types that are necessary to even talk about native
-- queries abstractly of a concrete implementation.
module Hasura.NativeQuery.Types
( NativeQueryName (..),
)
where
import Hasura.Prelude
newtype NativeQueryName = NativeQueryName {getNativeQueryName :: Text}
deriving (Eq, Ord, Show, Hashable)

View File

@ -381,6 +381,7 @@ data SelectFromG (b :: BackendType) v
(FunctionArgsExp b v)
-- a definition list
(Maybe [(Column b, ScalarType b)])
| FromNativeQuery (NativeQuery b v)
deriving stock (Generic)
deriving stock instance (Backend b) => Functor (SelectFromG b)
@ -389,11 +390,29 @@ deriving stock instance (Backend b) => Foldable (SelectFromG b)
deriving stock instance (Backend b) => Traversable (SelectFromG b)
deriving stock instance (Backend b, Eq v, Eq (FunctionArgumentExp b v)) => Eq (SelectFromG b v)
deriving stock instance
( Backend b,
Eq v,
Eq (FunctionArgumentExp b v),
Eq (NativeQuery b v)
) =>
Eq (SelectFromG b v)
deriving stock instance (Backend b, Show v, Show (FunctionArgumentExp b v)) => Show (SelectFromG b v)
deriving stock instance
( Backend b,
Show v,
Show (FunctionArgumentExp b v),
Show (NativeQuery b v)
) =>
Show (SelectFromG b v)
instance (Backend b, Hashable v, Hashable (FunctionArgumentExp b v)) => Hashable (SelectFromG b v)
instance
( Backend b,
Hashable v,
Hashable (FunctionArgumentExp b v),
Hashable (NativeQuery b v)
) =>
Hashable (SelectFromG b v)
type SelectFrom b = SelectFromG b (SQLExpression b)

View File

@ -178,7 +178,10 @@ class
Traversable (BackendInsert b),
Functor (AggregationPredicates b),
Foldable (AggregationPredicates b),
Traversable (AggregationPredicates b)
Traversable (AggregationPredicates b),
Functor (NativeQuery b),
Foldable (NativeQuery b),
Traversable (NativeQuery b)
) =>
Backend (b :: BackendType)
where
@ -309,6 +312,21 @@ class
type BackendInsert b = Const Void
-- | Intermediate representation of Native Queries
-- The default implementation makes native queries uninstantiable.
--
-- It is parameterised over the type of fields, which changes during the IR
-- translation phases.
type NativeQuery b :: Type -> Type
type NativeQuery b = Const Void
-- | Metadata representation of definitions of native queries.
-- The default implementation makes native queries uninstantiable.
type NativeQueryInfo b :: Type
type NativeQueryInfo b = Void
-- extension types
type XComputedField b :: Type
type XRelay b :: Type

View File

@ -18,6 +18,8 @@ module Hasura.RQL.Types.Source
siQueryTagsConfig,
siTables,
siCustomization,
NativeQueryCache,
_siNativeQueries,
-- * Schema cache
ResolvedSource (..),
@ -42,16 +44,23 @@ where
import Control.Lens hiding ((.=))
import Data.Aeson.Extended
import Data.HashMap.Strict qualified as HM
import Data.Maybe (fromJust)
import Data.Text.Lazy qualified as TL
import Data.Text.Lazy.Encoding qualified as BS
import Database.PG.Query qualified as PG
import Hasura.Base.Error
import Hasura.CustomSQL (CustomSQLParameter (..), CustomSQLParameterName (..), CustomSQLParameterType (..))
import Hasura.Logging qualified as L
import Hasura.NativeQuery.Metadata (NativeQueryArgumentName (..), NativeQueryInfoImpl (..))
import Hasura.NativeQuery.Types (NativeQueryName (..))
import Hasura.Prelude
import Hasura.RQL.Types.Backend
import Hasura.RQL.Types.Common
import Hasura.RQL.Types.Function
import Hasura.RQL.Types.HealthCheck
import Hasura.RQL.Types.Instances ()
import Hasura.RQL.Types.Metadata.Common (CustomSQLFields)
import Hasura.RQL.Types.Metadata.Common (CustomSQLFields, CustomSQLMetadata (..))
import Hasura.RQL.Types.QueryTags
import Hasura.RQL.Types.SourceCustomization
import Hasura.RQL.Types.Table
@ -60,6 +69,7 @@ import Hasura.SQL.Backend
import Hasura.SQL.Tag
import Hasura.Tracing qualified as Tracing
import Language.GraphQL.Draft.Syntax qualified as G
import Unsafe.Coerce (unsafeCoerce)
--------------------------------------------------------------------------------
-- Metadata (FIXME: this grouping is inaccurate)
@ -74,6 +84,45 @@ data SourceInfo b = SourceInfo
_siCustomization :: ResolvedSourceCustomization
}
-- This function is a temporary integration between metadata and schema of the Native Queries MVP.
-- It is **not** representative of the code quality we strive for, and will be properly dealt with.
_siNativeQueries :: forall b. Backend b => CustomSQLFields b -> NativeQueryCache b
_siNativeQueries = foldMap toItem
where
toItem :: CustomSQLMetadata b -> HashMap NativeQueryName (NativeQueryInfo b)
toItem csm = HM.fromList [(toNativeQueryName (_csmRootFieldName csm), toInfo csm)]
toNativeQueryName :: G.Name -> NativeQueryName
toNativeQueryName = NativeQueryName . G.unName
toInfo :: CustomSQLMetadata b -> NativeQueryInfo b
toInfo CustomSQLMetadata {..} =
-- '_siNativeQueries' would have to be defined in some type class over
-- 'b' in order to avoid this unsafeCoerce.
-- But since this is a temporary stop-gap which we won't release it's fine.
unsafeCoerce $ (NativeQueryInfoImpl {..} :: NativeQueryInfoImpl b)
where
nqiiName = toNativeQueryName _csmRootFieldName
nqiiCode = _csmSql
nqiiReturns = _csmReturns
nqiiArgs = toArgs _csmParameters
nqiiComment = "TBD"
toArgs :: NonEmpty CustomSQLParameter -> HashMap NativeQueryArgumentName (ScalarType b)
toArgs = foldMap toArg
toArg :: CustomSQLParameter -> HashMap NativeQueryArgumentName (ScalarType b)
toArg CustomSQLParameter {..} = HM.fromList [(toArgName cspName, toScalarType cspType)]
toArgName :: CustomSQLParameterName -> NativeQueryArgumentName
toArgName CustomSQLParameterName {..} = NativeQueryArgumentName cspnName
-- This mismatch is the worst part.
toScalarType :: CustomSQLParameterType -> ScalarType b
toScalarType CustomSQLParameterType {..} = fromJust $ decode (BS.encodeUtf8 $ TL.fromStrict cspnType)
type NativeQueryCache b = HashMap NativeQueryName (NativeQueryInfo b)
$(makeLenses ''SourceInfo)
instance