add a flag to disable native query validation

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10889
GitOrigin-RevId: 43f2b83fe99b1e0fd514ccaed4888694351fadfc
This commit is contained in:
Gil Mizrahi 2024-06-21 14:48:16 +03:00 committed by hasura-bot
parent b658c97a07
commit 4b884d3d47
17 changed files with 94 additions and 22 deletions

View File

@ -333,7 +333,8 @@ serveOptions =
soPersistedQueriesTtl = Init._default Init.persistedQueriesTtlOption,
soRemoteSchemaResponsePriority = Init._default Init.remoteSchemaResponsePriorityOption,
soHeaderPrecedence = Init._default Init.configuredHeaderPrecedenceOption,
soTraceQueryStatus = Init._default Init.traceQueryStatusOption
soTraceQueryStatus = Init._default Init.traceQueryStatusOption,
soDisableNativeQueryValidation = Init._default Init.disableNativeQueryValidationOption
}
-- | What log level should be used by the engine; this is not exported, and

View File

@ -115,6 +115,7 @@ import Hasura.GraphQL.Transport.WebSocket.Server qualified as WS
import Hasura.GraphQL.Transport.WebSocket.Types (WSServerEnv (..))
import Hasura.Logging
import Hasura.Metadata.Class
import Hasura.NativeQuery.Validation (DisableNativeQueryValidation)
import Hasura.PingSources
import Hasura.Prelude
import Hasura.QueryTags
@ -538,6 +539,7 @@ initialiseAppContext env serveOptions AppInit {..} = do
-- Create the schema cache
rebuildableSchemaCache <-
buildFirstSchemaCache
(soDisableNativeQueryValidation serveOptions)
env
logger
(mkPgSourceResolver pgLogger)
@ -601,6 +603,7 @@ migrateCatalogAndFetchMetadata
-- and avoid a breaking change.
buildFirstSchemaCache ::
(MonadIO m) =>
DisableNativeQueryValidation ->
Env.Environment ->
Logger Hasura ->
SourceResolver ('Postgres 'Vanilla) ->
@ -612,6 +615,7 @@ buildFirstSchemaCache ::
Maybe SchemaRegistry.SchemaRegistryContext ->
m RebuildableSchemaCache
buildFirstSchemaCache
disableNativeQueryValidation
env
logger
pgSourceResolver
@ -625,7 +629,7 @@ buildFirstSchemaCache
result <-
runExceptT
$ runCacheBuild cacheBuildParams
$ buildRebuildableSchemaCache logger env metadataWithVersion cacheDynamicConfig mSchemaRegistryContext
$ buildRebuildableSchemaCache logger env disableNativeQueryValidation metadataWithVersion cacheDynamicConfig mSchemaRegistryContext
result `onLeft` \err -> do
-- TODO: we used to bundle the first schema cache build with the catalog
-- migration, using the same error handler for both, meaning that an

View File

@ -34,6 +34,6 @@ instance BackendMetadata 'BigQuery where
listAllTables = BigQuery.listAllTables
listAllTrackables _ = throw400 UnexpectedPayload "listAllTrackables not supported by BigQuery!"
getTableInfo _ _ = throw400 UnexpectedPayload "get_table_info not yet supported in BigQuery!"
validateNativeQuery _ _ _ _ _ nq = do
validateNativeQuery _disableNativeQueryValidation _ _ _ _ _ nq = do
validateArgumentDeclaration nq
pure (trimQueryEnd (_nqmCode nq)) -- for now, all queries are valid

View File

@ -117,7 +117,7 @@ instance BackendMetadata 'DataConnector where
getTableInfo = getTableInfo'
supportsBeingRemoteRelationshipTarget = supportsBeingRemoteRelationshipTarget'
validateNativeQuery _ _ _ sc _ nq = do
validateNativeQuery _disableNativeQueryValidation _ _ _ sc _ nq = do
unless (isJust (API._cInterpolatedQueries (DC._scCapabilities sc))) do
let nqName = _nqmRootFieldName nq
throw400 NotSupported $ "validateNativeQuery: " <> toTxt nqName <> " - Native Queries not implemented for this Data Connector backend."

View File

@ -37,7 +37,7 @@ instance BackendMetadata 'MSSQL where
listAllTrackables _ =
throw500 "Computed fields are not yet defined for MSSQL backends"
getTableInfo _ _ = throw400 UnexpectedPayload "get_table_info not yet supported in MSSQL!"
validateNativeQuery _ _ _ _ _ nq = do
validateNativeQuery _disableNativeQueryValidation _ _ _ _ _ nq = do
validateArgumentDeclaration nq
pure (trimQueryEnd (_nqmCode nq)) -- for now, all queries are valid
validateStoredProcedure _ _ _ _ = pure () -- for now, all stored procedures are valid

View File

@ -36,7 +36,7 @@ import Hasura.NativeQuery.Metadata
NativeQueryMetadata (..),
)
import Hasura.NativeQuery.Types (NullableScalarType (nstType))
import Hasura.NativeQuery.Validation (validateArgumentDeclaration)
import Hasura.NativeQuery.Validation (DisableNativeQueryValidation (..), validateArgumentDeclaration)
import Hasura.Prelude
import Hasura.RQL.Types.BackendType
import Hasura.RQL.Types.Common (SourceName)
@ -46,6 +46,7 @@ validateNativeQuery ::
forall m pgKind sourceConfig.
(MonadIO m, MonadError QErr m) =>
InsOrdHashMap.InsOrdHashMap PGScalarType PQ.Oid ->
DisableNativeQueryValidation ->
Env.Environment ->
SourceName ->
PG.PostgresConnConfiguration ->
@ -53,15 +54,19 @@ validateNativeQuery ::
LogicalModelInfo ('Postgres pgKind) ->
NativeQueryMetadata ('Postgres pgKind) ->
m (InterpolatedQuery ArgumentName)
validateNativeQuery pgTypeOidMapping env sourceName connConf _sourceConfig logicalModel nq = do
validateNativeQuery pgTypeOidMapping disableNativeQueryValidation env sourceName connConf _sourceConfig logicalModel nq = do
validateArgumentDeclaration nq
let nqmCode = trimQueryEnd (_nqmCode nq)
model = nq {_nqmCode = nqmCode}
(prepname, preparedQuery) <- nativeQueryToPreparedStatement logicalModel model
description <- runCheck prepname (PG.fromText preparedQuery)
let returnColumns = bimap toTxt nstType <$> InsOrdHashMap.toList (columnsFromFields $ _lmiFields logicalModel)
case disableNativeQueryValidation of
NeverValidateNativeQueries ->
pure ()
AlwaysValidateNativeQueries -> do
(prepname, preparedQuery) <- nativeQueryToPreparedStatement logicalModel model
description <- runCheck prepname (PG.fromText preparedQuery)
let returnColumns = bimap toTxt nstType <$> InsOrdHashMap.toList (columnsFromFields $ _lmiFields logicalModel)
for_ (toList returnColumns) (matchTypes description)
for_ (toList returnColumns) (matchTypes description)
pure nqmCode
where
-- Run stuff against the database.

View File

@ -1,9 +1,11 @@
-- | Generic validation of native queries while tracking them.
module Hasura.NativeQuery.Validation
( validateArgumentDeclaration,
DisableNativeQueryValidation (..),
)
where
import Data.Aeson qualified as J
import Data.HashMap.Strict qualified as HashMap
import Data.Set (Set)
import Data.Set qualified as Set
@ -45,3 +47,23 @@ validateArgumentDeclaration NativeQueryMetadata {_nqmCode, _nqmArguments} = do
$ fromErrorMessage
$ "The following columns are declared as arguments, but are not used in the query: "
<> toErrorValue unusedArguments
-- | Should we validate Native Queries against the database?
-- Avoiding that could speed-up schema cache rebuild significantly.
data DisableNativeQueryValidation
= AlwaysValidateNativeQueries
| NeverValidateNativeQueries
deriving (Eq, Show)
instance J.FromJSON DisableNativeQueryValidation where
parseJSON =
J.withBool "DisableNativeQueryValidation"
$ pure
. \case
True -> NeverValidateNativeQueries
False -> AlwaysValidateNativeQueries
instance J.ToJSON DisableNativeQueryValidation where
toJSON = \case
AlwaysValidateNativeQueries -> J.Bool False
NeverValidateNativeQueries -> J.Bool True

View File

@ -61,6 +61,7 @@ import Hasura.Metadata.Class
import Hasura.NativeQuery.Cache (NativeQueryCache, NativeQueryInfo (..))
import Hasura.NativeQuery.Lenses (nqmReturns)
import Hasura.NativeQuery.Metadata (NativeQueryMetadata (..), getNativeQueryName)
import Hasura.NativeQuery.Validation (DisableNativeQueryValidation)
import Hasura.Prelude
import Hasura.QueryTags
import Hasura.RQL.DDL.Action
@ -162,14 +163,15 @@ action/function.
buildRebuildableSchemaCache ::
Logger Hasura ->
Env.Environment ->
DisableNativeQueryValidation ->
MetadataWithResourceVersion ->
CacheDynamicConfig ->
Maybe SchemaRegistryContext ->
CacheBuild RebuildableSchemaCache
buildRebuildableSchemaCache logger env metadataWithVersion dynamicConfig mSchemaRegistryContext = do
buildRebuildableSchemaCache logger env disableNativeQueryValidation metadataWithVersion dynamicConfig mSchemaRegistryContext = do
result <-
flip runReaderT CatalogSync
$ Inc.build (buildSchemaCacheRule logger env mSchemaRegistryContext) (metadataWithVersion, dynamicConfig, initialInvalidationKeys, Nothing)
$ Inc.build (buildSchemaCacheRule logger env disableNativeQueryValidation mSchemaRegistryContext) (metadataWithVersion, dynamicConfig, initialInvalidationKeys, Nothing)
pure $ RebuildableSchemaCache (fst $ Inc.result result) initialInvalidationKeys (Inc.rebuildRule result)
@ -446,10 +448,11 @@ buildSchemaCacheRule ::
) =>
Logger Hasura ->
Env.Environment ->
DisableNativeQueryValidation ->
Maybe SchemaRegistryContext ->
(MetadataWithResourceVersion, CacheDynamicConfig, InvalidationKeys, Maybe StoredIntrospection)
`arr` (SchemaCache, (SourcesIntrospectionStatus, SchemaRegistryAction))
buildSchemaCacheRule logger env mSchemaRegistryContext = proc (MetadataWithResourceVersion metadataNoDefaults interimMetadataResourceVersion, dynamicConfig, invalidationKeys, storedIntrospection) -> do
buildSchemaCacheRule logger env disableNativeQueryValidation mSchemaRegistryContext = proc (MetadataWithResourceVersion metadataNoDefaults interimMetadataResourceVersion, dynamicConfig, invalidationKeys, storedIntrospection) -> do
invalidationKeysDep <- Inc.newDependency -< invalidationKeys
let metadataDefaults = _cdcMetadataDefaults dynamicConfig
metadata@Metadata {..} = overrideMetadataDefaults metadataNoDefaults metadataDefaults
@ -1048,7 +1051,7 @@ buildSchemaCacheRule logger env mSchemaRegistryContext = proc (MetadataWithResou
_lmiDescription = _nqmDescription,
_lmiPermissions = logicalModelPermissions
}
nqmCode <- validateNativeQuery @b env sourceName (_smConfiguration sourceMetadata) sourceConfig logicalModel preValidationNativeQuery
nqmCode <- validateNativeQuery @b disableNativeQueryValidation env sourceName (_smConfiguration sourceMetadata) sourceConfig logicalModel preValidationNativeQuery
case maybeDependency of
Just dependency ->

View File

@ -14,6 +14,7 @@ import Hasura.Incremental qualified as Inc
import Hasura.Logging (Hasura, Logger)
import Hasura.LogicalModel.Cache (LogicalModelInfo)
import Hasura.NativeQuery.Metadata (ArgumentName, InterpolatedQuery, NativeQueryMetadata)
import Hasura.NativeQuery.Validation (DisableNativeQueryValidation)
import Hasura.Prelude
import Hasura.RQL.IR.BoolExp
import Hasura.RQL.Types.Backend
@ -242,6 +243,7 @@ class
validateNativeQuery ::
(MonadIO m, MonadError QErr m) =>
DisableNativeQueryValidation ->
Env.Environment ->
SourceName ->
SourceConnConfiguration b ->
@ -249,7 +251,7 @@ class
LogicalModelInfo b ->
NativeQueryMetadata b ->
m (InterpolatedQuery ArgumentName)
validateNativeQuery _ _ _ _ _ _ =
validateNativeQuery _ _ _ _ _ _ _ =
throw500 "validateNativeQuery: not implemented for this backend."
validateStoredProcedure ::

View File

@ -34,6 +34,7 @@ import Hasura.Base.Error qualified as Error
import Hasura.GraphQL.ApolloFederation (getApolloFederationStatus)
import Hasura.GraphQL.Execute.Subscription.Options qualified as Subscription.Options
import Hasura.Logging qualified as Logging
import Hasura.NativeQuery.Validation qualified as NativeQuery
import Hasura.Prelude
import Hasura.RQL.Types.Common qualified as Common
import Hasura.RQL.Types.Schema.Options qualified as Options
@ -226,6 +227,10 @@ mkServeOptions sor@ServeOptionsRaw {..} = do
soRemoteSchemaResponsePriority <- withOptionDefault rsoRemoteSchemaResponsePriority remoteSchemaResponsePriorityOption
soHeaderPrecedence <- withOptionDefault rsoHeaderPrecedence configuredHeaderPrecedenceOption
soTraceQueryStatus <- withOptionDefault rsoTraceQueryStatus traceQueryStatusOption
soDisableNativeQueryValidation <-
case rsoDisableNativeQueryValidation of
NativeQuery.AlwaysValidateNativeQueries -> withOptionDefault Nothing disableNativeQueryValidationOption
NativeQuery.NeverValidateNativeQueries -> pure NativeQuery.NeverValidateNativeQueries
pure ServeOptions {..}
-- | Fetch Postgres 'Query.ConnParams' components from the environment

View File

@ -31,6 +31,7 @@ module Hasura.Server.Init.Arg.Command.Serve
enableTelemetryOption,
wsReadCookieOption,
stringifyNumOption,
disableNativeQueryValidationOption,
dangerousBooleanCollapseOption,
backwardsCompatibleNullInNonNullableVariablesOption,
remoteNullForwardingPolicyOption,
@ -88,6 +89,7 @@ import Hasura.Backends.Postgres.Connection.MonadTx qualified as MonadTx
import Hasura.Cache.Bounded qualified as Bounded
import Hasura.GraphQL.Execute.Subscription.Options qualified as Subscription.Options
import Hasura.Logging qualified as Logging
import Hasura.NativeQuery.Validation qualified as NativeQuery
import Hasura.Prelude
import Hasura.RQL.Types.Metadata (MetadataDefaults, emptyMetadataDefaults)
import Hasura.RQL.Types.NamingCase qualified as NC
@ -169,6 +171,7 @@ serveCommandParser =
<*> parseRemoteSchemaResponsePriority
<*> parseConfiguredHeaderPrecedence
<*> parseTraceQueryStatus
<*> parseDisableNativeQueryValidation
--------------------------------------------------------------------------------
-- Serve Options
@ -627,6 +630,22 @@ stringifyNumOption =
Config._helpMessage = "Stringify numeric types (default: false)"
}
parseDisableNativeQueryValidation :: Opt.Parser NativeQuery.DisableNativeQueryValidation
parseDisableNativeQueryValidation =
fmap (bool NativeQuery.AlwaysValidateNativeQueries NativeQuery.NeverValidateNativeQueries)
$ Opt.switch
( Opt.long "disable-native-query-validation"
<> Opt.help (Config._helpMessage disableNativeQueryValidationOption)
)
disableNativeQueryValidationOption :: Config.Option NativeQuery.DisableNativeQueryValidation
disableNativeQueryValidationOption =
Config.Option
{ Config._default = NativeQuery.AlwaysValidateNativeQueries,
Config._envVar = "HASURA_GRAPHQL_DISABLE_NATIVE_QUERY_VALIDATION",
Config._helpMessage = "Disable Native Query validation (default: false)"
}
parseDangerousBooleanCollapse :: Opt.Parser (Maybe Options.DangerouslyCollapseBooleans)
parseDangerousBooleanCollapse =
Opt.optional

View File

@ -90,6 +90,7 @@ import Database.PG.Query qualified as Query
import Hasura.Backends.Postgres.Connection.MonadTx qualified as MonadTx
import Hasura.GraphQL.Execute.Subscription.Options qualified as Subscription.Options
import Hasura.Logging qualified as Logging
import Hasura.NativeQuery.Validation qualified as NativeQuery.Validation
import Hasura.Prelude
import Hasura.RQL.Types.Common qualified as Common
import Hasura.RQL.Types.Metadata (MetadataDefaults)
@ -333,7 +334,8 @@ data ServeOptionsRaw impl = ServeOptionsRaw
rsoPersistedQueriesTtl :: Maybe Int,
rsoRemoteSchemaResponsePriority :: Maybe Server.Types.RemoteSchemaResponsePriority,
rsoHeaderPrecedence :: Maybe Server.Types.HeaderPrecedence,
rsoTraceQueryStatus :: Maybe Server.Types.TraceQueryStatus
rsoTraceQueryStatus :: Maybe Server.Types.TraceQueryStatus,
rsoDisableNativeQueryValidation :: NativeQuery.Validation.DisableNativeQueryValidation
}
deriving stock instance (Show (Logging.EngineLogType impl)) => Show (ServeOptionsRaw impl)
@ -645,7 +647,8 @@ data ServeOptions impl = ServeOptions
soPersistedQueriesTtl :: Int,
soRemoteSchemaResponsePriority :: Server.Types.RemoteSchemaResponsePriority,
soHeaderPrecedence :: Server.Types.HeaderPrecedence,
soTraceQueryStatus :: Server.Types.TraceQueryStatus
soTraceQueryStatus :: Server.Types.TraceQueryStatus,
soDisableNativeQueryValidation :: NativeQuery.Validation.DisableNativeQueryValidation
}
-- | 'ResponseInternalErrorsConfig' represents the encoding of the

View File

@ -35,6 +35,7 @@ import Hasura.Backends.Postgres.Connection.MonadTx qualified as MonadTx
import Hasura.Cache.Bounded qualified as Cache
import Hasura.GraphQL.Execute.Subscription.Options qualified as Subscription.Options
import Hasura.Logging qualified as Logging
import Hasura.NativeQuery.Validation qualified as NativeQuery
import Hasura.Prelude
import Hasura.RQL.Types.Metadata (Metadata, MetadataDefaults (..))
import Hasura.RQL.Types.NamingCase (NamingCase)
@ -247,6 +248,9 @@ instance FromEnv Metadata where
instance FromEnv Options.StringifyNumbers where
fromEnv = fmap (bool Options.Don'tStringifyNumbers Options.StringifyNumbers) . fromEnv @Bool
instance FromEnv NativeQuery.DisableNativeQueryValidation where
fromEnv = fmap (bool NativeQuery.AlwaysValidateNativeQueries NativeQuery.NeverValidateNativeQueries) . fromEnv @Bool
instance FromEnv Options.RemoteSchemaPermissions where
fromEnv = fmap (bool Options.DisableRemoteSchemaPermissions Options.EnableRemoteSchemaPermissions) . fromEnv @Bool

View File

@ -13,6 +13,7 @@ import Database.PG.Query qualified as Query
import Hasura.GraphQL.Execute.Subscription.Options qualified as Subscription.Options
import Hasura.Logging (Hasura)
import Hasura.Logging qualified as Logging
import Hasura.NativeQuery.Validation qualified as NativeQuery
import Hasura.Prelude
import Hasura.RQL.Types.NamingCase qualified as NamingCase
import Hasura.RQL.Types.Roles qualified as Roles
@ -102,7 +103,8 @@ emptyServeOptionsRaw =
rsoPersistedQueriesTtl = Nothing,
rsoRemoteSchemaResponsePriority = Nothing,
rsoHeaderPrecedence = Nothing,
rsoTraceQueryStatus = Nothing
rsoTraceQueryStatus = Nothing,
rsoDisableNativeQueryValidation = NativeQuery.AlwaysValidateNativeQueries
}
mkServeOptionsSpec :: Hspec.Spec

View File

@ -101,7 +101,8 @@ serveOptions =
soPersistedQueriesTtl = Init._default Init.persistedQueriesTtlOption,
soRemoteSchemaResponsePriority = Init._default Init.remoteSchemaResponsePriorityOption,
soHeaderPrecedence = Init._default Init.configuredHeaderPrecedenceOption,
soTraceQueryStatus = Init._default Init.traceQueryStatusOption
soTraceQueryStatus = Init._default Init.traceQueryStatusOption,
soDisableNativeQueryValidation = Init._default Init.disableNativeQueryValidationOption
}
-- | What log level should be used by the engine; this is not exported, and

View File

@ -156,7 +156,7 @@ main = do
snd
<$> (liftEitherM . runExceptT . _pecRunTx pgContext (PGExecCtxInfo (Tx PG.ReadWrite Nothing) InternalRawQuery))
(migrateCatalog (Just sourceConfig) defaultPostgresExtensionsSchema maintenanceMode =<< liftIO getCurrentTime)
schemaCache <- runCacheBuild cacheBuildParams $ buildRebuildableSchemaCache logger envMap metadataWithVersion dynamicConfig Nothing
schemaCache <- runCacheBuild cacheBuildParams $ buildRebuildableSchemaCache logger envMap (soDisableNativeQueryValidation serveOptions) metadataWithVersion dynamicConfig Nothing
pure (_mwrvMetadata metadataWithVersion, schemaCache)
cacheRef <- newMVar schemaCache

View File

@ -16,6 +16,7 @@ import Hasura.Base.Error
import Hasura.EncJSON
import Hasura.Logging
import Hasura.Metadata.Class
import Hasura.NativeQuery.Validation (DisableNativeQueryValidation (AlwaysValidateNativeQueries))
import Hasura.Prelude
import Hasura.RQL.DDL.EventTrigger (MonadEventLogCleanup (..))
import Hasura.RQL.DDL.Metadata (ClearMetadata (..), runClearMetadata)
@ -136,7 +137,7 @@ suite srcConfig pgExecCtx pgConnInfo = do
migrateCatalogAndBuildCache env time = do
dynamicConfig <- asks fst
(migrationResult, metadataWithVersion) <- runTx' pgExecCtx $ migrateCatalog (Just srcConfig) (ExtensionsSchema "public") MaintenanceModeDisabled time
(,migrationResult) <$> runCacheBuildM (buildRebuildableSchemaCache logger env metadataWithVersion dynamicConfig Nothing)
(,migrationResult) <$> runCacheBuildM (buildRebuildableSchemaCache logger env AlwaysValidateNativeQueries metadataWithVersion dynamicConfig Nothing)
dropAndInit env time = lift do
scVar <- asks snd