mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
6aae78c8e1
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10013 GitOrigin-RevId: 3843e379e08bcb65e65110fc8217acc05cf7640a
253 lines
8.5 KiB
Haskell
253 lines
8.5 KiB
Haskell
{-# LANGUAGE UndecidableInstances #-}
|
|
|
|
-- | Define and handle v1/metadata API operations to track, untrack, and get stored procedures.
|
|
module Hasura.StoredProcedure.API
|
|
( GetStoredProcedure (..),
|
|
TrackStoredProcedure (..),
|
|
UntrackStoredProcedure (..),
|
|
execTrackStoredProcedure,
|
|
execUntrackStoredProcedure,
|
|
runGetStoredProcedure,
|
|
dropStoredProcedureInMetadata,
|
|
module Hasura.StoredProcedure.Types,
|
|
)
|
|
where
|
|
|
|
import Autodocodec (HasCodec)
|
|
import Autodocodec qualified as AC
|
|
import Control.Lens (Traversal', has, preview, (^?))
|
|
import Data.Aeson
|
|
import Data.HashMap.Strict.InsOrd.Extended qualified as InsOrdHashMap
|
|
import Data.Text.Extended (toTxt, (<<>))
|
|
import Hasura.Base.Error
|
|
import Hasura.EncJSON
|
|
import Hasura.LogicalModel.Metadata (LogicalModelName)
|
|
import Hasura.Prelude
|
|
import Hasura.RQL.Types.Backend (Backend, FunctionName)
|
|
import Hasura.RQL.Types.BackendTag
|
|
import Hasura.RQL.Types.BackendType
|
|
import Hasura.RQL.Types.Common
|
|
( SourceName,
|
|
sourceNameToText,
|
|
)
|
|
import Hasura.RQL.Types.Metadata
|
|
import Hasura.RQL.Types.Metadata.Backend
|
|
import Hasura.RQL.Types.Metadata.Object
|
|
import Hasura.SQL.AnyBackend qualified as AB
|
|
import Hasura.StoredProcedure.Metadata (ArgumentName, StoredProcedureMetadata (..))
|
|
import Hasura.StoredProcedure.Types
|
|
|
|
-- | Default implementation of the 'track_stored_procedure_query' request payload.
|
|
data TrackStoredProcedure (b :: BackendType) = TrackStoredProcedure
|
|
{ tspSource :: SourceName,
|
|
tspStoredProcedure :: FunctionName b,
|
|
tspConfig :: StoredProcedureConfig,
|
|
tspArguments :: HashMap ArgumentName (NullableScalarType b),
|
|
tspDescription :: Maybe Text,
|
|
tspReturns :: LogicalModelName
|
|
}
|
|
|
|
instance (Backend b) => HasCodec (TrackStoredProcedure b) where
|
|
codec =
|
|
AC.CommentCodec
|
|
("A request to track a stored procedure")
|
|
$ AC.object (backendPrefix @b <> "TrackStoredProcedure")
|
|
$ TrackStoredProcedure
|
|
<$> AC.requiredField "source" sourceDoc
|
|
AC..= tspSource
|
|
<*> AC.requiredField "stored_procedure" spDoc
|
|
AC..= tspStoredProcedure
|
|
<*> AC.requiredField "configuration" configDoc
|
|
AC..= tspConfig
|
|
<*> AC.optionalFieldWithDefault "arguments" mempty argumentsDoc
|
|
AC..= tspArguments
|
|
<*> AC.optionalField "description" descriptionDoc
|
|
AC..= tspDescription
|
|
<*> AC.requiredField "returns" returnsDoc
|
|
AC..= tspReturns
|
|
where
|
|
sourceDoc = "The source in which this stored procedure should be tracked"
|
|
configDoc = "The configuration for the SQL stored procedure"
|
|
spDoc = "The name of the SQL stored procedure"
|
|
argumentsDoc = "Free variables in the expression and their types"
|
|
returnsDoc = "Return type (table) of the expression"
|
|
descriptionDoc = "A description of the query which appears in the graphql schema"
|
|
|
|
deriving via
|
|
(AC.Autodocodec (TrackStoredProcedure b))
|
|
instance
|
|
(Backend b) => FromJSON (TrackStoredProcedure b)
|
|
|
|
deriving via
|
|
(AC.Autodocodec (TrackStoredProcedure b))
|
|
instance
|
|
(Backend b) => ToJSON (TrackStoredProcedure b)
|
|
|
|
-- Configuration
|
|
|
|
-- | API payload for the 'get_stored_procedure' endpoint.
|
|
data GetStoredProcedure (b :: BackendType) = GetStoredProcedure
|
|
{ gspSource :: SourceName
|
|
}
|
|
|
|
deriving instance (Backend b) => Show (GetStoredProcedure b)
|
|
|
|
deriving instance (Backend b) => Eq (GetStoredProcedure b)
|
|
|
|
instance (Backend b) => FromJSON (GetStoredProcedure b) where
|
|
parseJSON = withObject "GetStoredProcedure" $ \o -> do
|
|
gspSource <- o .: "source"
|
|
pure GetStoredProcedure {..}
|
|
|
|
instance (Backend b) => ToJSON (GetStoredProcedure b) where
|
|
toJSON GetStoredProcedure {..} =
|
|
object
|
|
[ "source" .= gspSource
|
|
]
|
|
|
|
-- | Handler for the 'get_stored_procedure' endpoint.
|
|
runGetStoredProcedure ::
|
|
forall b m.
|
|
( BackendMetadata b,
|
|
MetadataM m
|
|
) =>
|
|
GetStoredProcedure b ->
|
|
m EncJSON
|
|
runGetStoredProcedure q = do
|
|
metadata <- getMetadata
|
|
|
|
let storedProcedure :: Maybe (StoredProcedures b)
|
|
storedProcedure = metadata ^? metaSources . ix (gspSource q) . toSourceMetadata . smStoredProcedures @b
|
|
|
|
pure (encJFromJValue (InsOrdHashMap.elems <$> storedProcedure))
|
|
|
|
-- | Handler for the 'track_stored_procedure' endpoint. The type 'TrackStoredProcedure b'
|
|
-- (appearing here in wrapped as 'BackendTrackStoredProcedure b' for 'AnyBackend'
|
|
-- compatibility) is defined in 'class StoredProcedureMetadata'.
|
|
execTrackStoredProcedure ::
|
|
forall b m.
|
|
( BackendMetadata b,
|
|
MonadError QErr m
|
|
) =>
|
|
TrackStoredProcedure b ->
|
|
Metadata ->
|
|
m (MetadataObjId, MetadataModifier)
|
|
execTrackStoredProcedure TrackStoredProcedure {..} metadata = do
|
|
sourceMetadata <-
|
|
maybe
|
|
( throw400 NotFound
|
|
$ "Source '"
|
|
<> sourceNameToText tspSource
|
|
<> "' of kind "
|
|
<> toTxt (reify (backendTag @b))
|
|
<> " not found."
|
|
)
|
|
pure
|
|
. preview (metaSources . ix tspSource . toSourceMetadata @b)
|
|
$ metadata
|
|
|
|
let spMetadata =
|
|
StoredProcedureMetadata
|
|
{ _spmStoredProcedure = tspStoredProcedure,
|
|
_spmConfig = tspConfig,
|
|
_spmReturns = tspReturns,
|
|
_spmArguments = tspArguments,
|
|
_spmDescription = tspDescription
|
|
}
|
|
|
|
let storedProcedure = _spmStoredProcedure spMetadata
|
|
metadataObj =
|
|
MOSourceObjId tspSource
|
|
$ AB.mkAnyBackend
|
|
$ SMOStoredProcedure @b storedProcedure
|
|
existingStoredProcedures = InsOrdHashMap.keys (_smStoredProcedures sourceMetadata)
|
|
when (storedProcedure `elem` existingStoredProcedures) do
|
|
throw400 AlreadyTracked $ "Stored procedure '" <> toTxt storedProcedure <> "' is already tracked."
|
|
|
|
let metadataModifier =
|
|
MetadataModifier
|
|
$ (metaSources . ix tspSource . toSourceMetadata @b . smStoredProcedures)
|
|
%~ InsOrdHashMap.insert storedProcedure spMetadata
|
|
|
|
pure (metadataObj, metadataModifier)
|
|
|
|
-- | API payload for the 'untrack_stored_procedure' endpoint.
|
|
data UntrackStoredProcedure (b :: BackendType) = UntrackStoredProcedure
|
|
{ utspSource :: SourceName,
|
|
utspStoredProcedure :: FunctionName b
|
|
}
|
|
|
|
deriving instance (Backend b) => Show (UntrackStoredProcedure b)
|
|
|
|
deriving instance (Backend b) => Eq (UntrackStoredProcedure b)
|
|
|
|
instance (Backend b) => FromJSON (UntrackStoredProcedure b) where
|
|
parseJSON = withObject "UntrackStoredProcedure" $ \o -> do
|
|
utspSource <- o .: "source"
|
|
utspStoredProcedure <- o .: "stored_procedure"
|
|
pure UntrackStoredProcedure {..}
|
|
|
|
instance (Backend b) => ToJSON (UntrackStoredProcedure b) where
|
|
toJSON UntrackStoredProcedure {..} =
|
|
object
|
|
[ "source" .= utspSource,
|
|
"stored_procedure" .= utspStoredProcedure
|
|
]
|
|
|
|
-- | Handler for the 'untrack_stored_procedure' endpoint.
|
|
execUntrackStoredProcedure ::
|
|
forall b m.
|
|
( BackendMetadata b,
|
|
MonadError QErr m
|
|
) =>
|
|
UntrackStoredProcedure b ->
|
|
Metadata ->
|
|
m (MetadataObjId, MetadataModifier)
|
|
execUntrackStoredProcedure (UntrackStoredProcedure {..}) metadata = do
|
|
-- we do not check for feature flag here as we always want users to be able
|
|
-- to remove stored procedures if they'd like
|
|
assertStoredProcedureExists @b utspSource utspStoredProcedure metadata
|
|
|
|
let metadataObj =
|
|
MOSourceObjId utspSource
|
|
$ AB.mkAnyBackend
|
|
$ SMOStoredProcedure @b utspStoredProcedure
|
|
|
|
pure (metadataObj, dropStoredProcedureInMetadata @b utspSource utspStoredProcedure)
|
|
|
|
dropStoredProcedureInMetadata ::
|
|
forall b.
|
|
(BackendMetadata b) =>
|
|
SourceName ->
|
|
FunctionName b ->
|
|
MetadataModifier
|
|
dropStoredProcedureInMetadata source rootFieldName = do
|
|
MetadataModifier
|
|
$ metaSources
|
|
. ix source
|
|
. toSourceMetadata @b
|
|
. smStoredProcedures
|
|
%~ InsOrdHashMap.delete rootFieldName
|
|
|
|
-- | Check whether a stored procedure exists for the given source.
|
|
assertStoredProcedureExists ::
|
|
forall b m.
|
|
(Backend b, MonadError QErr m) =>
|
|
SourceName ->
|
|
FunctionName b ->
|
|
Metadata ->
|
|
m ()
|
|
assertStoredProcedureExists sourceName storedProcedure metadata = do
|
|
let sourceMetadataTraversal :: Traversal' Metadata (SourceMetadata b)
|
|
sourceMetadataTraversal = metaSources . ix sourceName . toSourceMetadata @b
|
|
|
|
sourceMetadata <-
|
|
preview sourceMetadataTraversal metadata
|
|
`onNothing` throw400 NotFound ("Source " <> sourceName <<> " not found.")
|
|
|
|
let desiredStoredProcedure :: Traversal' (SourceMetadata b) (StoredProcedureMetadata b)
|
|
desiredStoredProcedure = smStoredProcedures . ix storedProcedure
|
|
|
|
unless (has desiredStoredProcedure sourceMetadata) do
|
|
throw400 NotFound ("Stored Procedure " <> toTxt storedProcedure <<> " not found in source " <> sourceName <<> ".")
|