Skip to content

Commit 664c43b

Browse files
committed
improve - server app monad type & metadata storage class description
1 parent d807d10 commit 664c43b

File tree

5 files changed

+57
-103
lines changed

5 files changed

+57
-103
lines changed

server/src-exec/Main.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ runApp env (HGEOptionsG rci hgeCmd) =
6565
Signals.sigTERM
6666
(Signals.CatchOnce (shutdownGracefully initCtx))
6767
Nothing
68-
flip runReaderT (_icPgPool initCtx) $ unServerAppM $
68+
flip runPGMetadataStorageApp (_icPgPool initCtx) $
6969
runHGEServer env serveOptions initCtx Nothing initTime shutdownApp Nothing ekgStore
7070

7171
HCExport -> do

server/src-lib/Hasura/App.hs

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,14 @@ data Loggers
176176
, _lsPgLogger :: !Q.PGLogger
177177
}
178178

179-
newtype ServerAppM a = ServerAppM { unServerAppM :: ReaderT Q.PGPool IO a }
179+
-- | An application with Postgres database as a metadata storage
180+
newtype PGMetadataStorageApp a
181+
= PGMetadataStorageApp {runPGMetadataStorageApp :: Q.PGPool -> IO a}
180182
deriving ( Functor, Applicative, Monad
181183
, MonadIO, MonadBase IO, MonadBaseControl IO
182184
, MonadCatch, MonadThrow, MonadMask
183-
, MonadReader Q.PGPool
184-
, MonadUnique
185-
)
185+
, MonadUnique, MonadReader Q.PGPool
186+
) via (ReaderT Q.PGPool IO)
186187

187188
-- | this function initializes the catalog and returns an @InitCtx@, based on the command given
188189
-- - for serve command it creates a proper PG connection pool
@@ -629,12 +630,12 @@ execQuery env queryBs = do
629630
buildSchemaCacheStrict
630631
encJToLBS <$> runQueryM env query
631632

632-
instance Tracing.HasReporter ServerAppM
633+
instance Tracing.HasReporter PGMetadataStorageApp
633634

634-
instance MonadQueryInstrumentation ServerAppM where
635+
instance MonadQueryInstrumentation PGMetadataStorageApp where
635636
askInstrumentQuery _ = pure (id, noProfile)
636637

637-
instance HttpLog ServerAppM where
638+
instance HttpLog PGMetadataStorageApp where
638639
logHttpError logger userInfoM reqId waiReq req qErr headers =
639640
unLogger logger $ mkHttpLog $
640641
mkHttpErrorLogContext userInfoM reqId waiReq req qErr Nothing Nothing headers
@@ -643,57 +644,58 @@ instance HttpLog ServerAppM where
643644
unLogger logger $ mkHttpLog $
644645
mkHttpAccessLogContext userInfoM reqId waiReq compressedResponse qTime cType headers
645646

646-
instance MonadExecuteQuery ServerAppM where
647+
instance MonadExecuteQuery PGMetadataStorageApp where
647648
cacheLookup _ _ = pure ([], Nothing)
648649
cacheStore _ _ = pure ()
649650

650-
instance UserAuthentication (Tracing.TraceT ServerAppM) where
651+
instance UserAuthentication (Tracing.TraceT PGMetadataStorageApp) where
651652
resolveUserInfo logger manager headers authMode =
652653
runExceptT $ getUserInfoWithExpTime logger manager headers authMode
653654

654-
instance MetadataApiAuthorization ServerAppM where
655+
instance MetadataApiAuthorization PGMetadataStorageApp where
655656
authorizeMetadataApi query userInfo = do
656657
let currRole = _uiRole userInfo
657658
when (requiresAdmin query && currRole /= adminRoleName) $
658659
withPathK "args" $ throw400 AccessDenied errMsg
659660
where
660661
errMsg = "restricted access : admin only"
661662

662-
instance ConsoleRenderer ServerAppM where
663+
instance ConsoleRenderer PGMetadataStorageApp where
663664
renderConsole path authMode enableTelemetry consoleAssetsDir =
664665
return $ mkConsoleHTML path authMode enableTelemetry consoleAssetsDir
665666

666-
instance MonadGQLExecutionCheck ServerAppM where
667+
instance MonadGQLExecutionCheck PGMetadataStorageApp where
667668
checkGQLExecution userInfo _ enableAL sc query = runExceptT $ do
668669
req <- toParsed query
669670
checkQueryInAllowlist enableAL userInfo req sc
670671
return req
671672

672-
instance MonadConfigApiHandler ServerAppM where
673+
instance MonadConfigApiHandler PGMetadataStorageApp where
673674
runConfigApiHandler = configApiGetHandler
674675

675-
instance MonadQueryLog ServerAppM where
676+
instance MonadQueryLog PGMetadataStorageApp where
676677
logQueryLog logger query genSqlM reqId =
677678
unLogger logger $ QueryLog query genSqlM reqId
678679

679-
instance WS.MonadWSLog ServerAppM where
680+
instance WS.MonadWSLog PGMetadataStorageApp where
680681
logWSLog = unLogger
681682

682-
runTxInMetadataStorage :: Q.TxE QErr a -> MetadataStorageT ServerAppM a
683-
runTxInMetadataStorage tx = do
684-
pool <- lift ask
685-
liftEitherM $ liftIO $ runExceptT $
686-
Q.runTx pool (Q.RepeatableRead, Just Q.ReadWrite) tx
687-
688-
instance MonadMetadataStorage (MetadataStorageT ServerAppM) where
689-
690-
getDeprivedCronTriggerStats = runTxInMetadataStorage getDeprivedCronTriggerStatsTx
691-
getScheduledEventsForDelivery = runTxInMetadataStorage getScheduledEventsForDeliveryTx
692-
insertScheduledEvent = runTxInMetadataStorage . insertScheduledEventTx
693-
insertScheduledEventInvocation a b = runTxInMetadataStorage $ insertInvocationTx a b
694-
setScheduledEventOp a b c = runTxInMetadataStorage $ setScheduledEventOpTx a b c
695-
unlockScheduledEvents a b = runTxInMetadataStorage $ unlockScheduledEventsTx a b
696-
unlockAllLockedScheduledEvents = runTxInMetadataStorage unlockAllLockedScheduledEventsTx
683+
instance MonadTx (MetadataStorageT PGMetadataStorageApp) where
684+
liftTx tx = do
685+
pool <- lift ask
686+
-- Each operation in metadata storage is executed in an isolated transaction.
687+
-- No two operations are not necessarily executed together.
688+
liftEitherM $ liftIO $ runExceptT $ Q.runTx pool (Q.RepeatableRead, Nothing) tx
689+
690+
instance MonadMetadataStorage (MetadataStorageT PGMetadataStorageApp) where
691+
692+
getDeprivedCronTriggerStats = liftTx getDeprivedCronTriggerStatsTx
693+
getScheduledEventsForDelivery = liftTx getScheduledEventsForDeliveryTx
694+
insertScheduledEvent = liftTx . insertScheduledEventTx
695+
insertScheduledEventInvocation a b = liftTx $ insertInvocationTx a b
696+
setScheduledEventOp a b c = liftTx $ setScheduledEventOpTx a b c
697+
unlockScheduledEvents a b = liftTx $ unlockScheduledEventsTx a b
698+
unlockAllLockedScheduledEvents = liftTx unlockAllLockedScheduledEventsTx
697699

698700
--- helper functions ---
699701

server/src-lib/Hasura/Eventing/ScheduledTrigger.hs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,8 @@ runCronEventsGenerator logger getSC = do
139139
mapM (withCronTrigger cronTriggersCache) deprivedCronTriggerStats
140140
insertCronEventsFor cronTriggersForHydrationWithStats
141141

142-
case eitherRes of
143-
Right _ -> pure ()
144-
Left err -> L.unLogger logger $
145-
ScheduledTriggerInternalErr $ err500 Unexpected (T.pack $ show err)
142+
onLeft eitherRes $ L.unLogger logger .
143+
ScheduledTriggerInternalErr . err500 Unexpected . T.pack . show
146144

147145
liftIO $ sleep (minutes 1)
148146
where

server/src-lib/Hasura/Metadata/Class.hs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
-- | This module has type class and types which implements the Metadata Storage Abstraction
2+
{-# LANGUAGE UndecidableInstances #-}
23
module Hasura.Metadata.Class
34
( MetadataStorageT(..)
45
, runMetadataStorageT
@@ -15,6 +16,7 @@ import Hasura.RQL.Types
1516

1617
import qualified Hasura.Tracing as Tracing
1718

19+
-- | Metadata storage transformer that enables raising @'QErr' exceptions.
1820
newtype MetadataStorageT m a
1921
= MetadataStorageT {unMetadataStorageT :: ExceptT QErr m a}
2022
deriving ( Functor, Applicative, Monad
@@ -32,38 +34,49 @@ runMetadataStorageT =
3234

3335
{- Note [Todo: Common interface for eventing sub-system]
3436
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35-
Scheduled and Table event triggers have their own implementation
36-
and tables in Postgres. The core logic for both is same. So it
37-
is necessary to have a unified interface (maybe via a Postgres extension)
38-
where all event triggers implementation can extend from. By this we can
39-
fairly reduce the Postgres schema foot print and interactions to the database.
37+
Postgres tables' event triggers and scheduled event triggers are similar in the
38+
core logic. But currently, their implementation is completely isolated and do not share
39+
a common schema in Postgres. We're having a plan to simplify them via a common interface
40+
(maybe via a Postgres extension). This will potentially reduce number of interactions made
41+
to database and schema foot print.
4042
4143
TODO: Reference to open issue or rfc?
4244
-}
4345

4446
-- | Metadata storage abstraction via a type class.
4547
--
48+
-- This type class enables storing and managing Hasura metadata in an isolated
49+
-- database which will not interfere with user's database where tables/functions
50+
-- are defined. Hence, it'll enable support for databases of multiple backends
51+
-- like MySQL, MSSQL etc.
52+
--
4653
-- This class has functions broadly related to:
4754
--
48-
-- 1. Metadata Management (TODO)
49-
-- ---------------------
55+
-- 1. Metadata Management
56+
-- ----------------------
57+
-- TODO
5058
-- Basic metadata management functions such as retrieving metadata from storage
5159
-- database and replacing the given metadata.
5260
--
5361
-- 2. Scheduled Triggers
5462
-- ---------------------
5563
-- Eventing sub-system for scheduled triggers is implemented via metadata storage.
5664
-- All necessary functions are included in the type class.
57-
-- (TODO):
65+
-- TODO
5866
-- This also includes functions to fetch events and their invocations so that the
5967
-- console can show them in the UI.
6068
-- For more details, refer description in 'Hasura.Eventing.ScheduledTrigger' module.
6169
--
62-
-- 3. Async Actions (TODO)
70+
-- 3. Async Actions
6371
-- ----------------
72+
-- TODO
6473
-- Operations to implement async actions sub-system. This includes recording an
6574
-- async action event and retreiving the details of action delivery to the webhook.
6675
-- For more details see Note [Async action architecture] in 'Hasura.GraphQL.Execute.Action' module.
76+
--
77+
-- It is believed that all the above three are implemented in a single storage
78+
-- system (ex: a Postgres database). We can split the functions into appropriate and
79+
-- specific type classes in future iterations if required.
6780

6881
class (MonadError QErr m) => MonadMetadataStorage m where
6982

server/src-lib/Hasura/RQL/Types/ScheduledTrigger.hs

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,14 @@ module Hasura.RQL.Types.ScheduledTrigger
1717
, scheduledEventStatusToText
1818
, ScheduledEventType(..)
1919
, ScheduledEvent(..)
20-
, GetInvocationsBy(..)
21-
, ScheduledEventPagination(..)
2220
, ScheduledEventInvocation(..)
23-
, GetEventInvocations(..)
2421
, OneOffScheduledEvent(..)
2522
, CronEvent(..)
2623
) where
2724

2825
import Data.Aeson
2926
import Data.Aeson.Casing
3027
import Data.Aeson.TH
31-
import Data.Aeson.Types
3228
import Data.Time.Clock
3329
import Data.Time.Clock.Units
3430
import Data.Time.Format.ISO8601
@@ -222,61 +218,6 @@ data ScheduledEvent
222218
| SECron !TriggerName
223219
deriving (Show, Eq)
224220

225-
parseScheduledEvent :: Object -> Parser ScheduledEvent
226-
parseScheduledEvent o = do
227-
ty <- o .: "type"
228-
case ty of
229-
Cron -> SECron <$> o .: "trigger_name"
230-
OneOff -> pure SEOneOff
231-
232-
scheduledEventToPairs :: ScheduledEvent -> [Pair]
233-
scheduledEventToPairs = \case
234-
SEOneOff -> ["type" .= OneOff]
235-
SECron name -> ["type" .= Cron, "trigger_name" .= name]
236-
237-
data ScheduledEventPagination
238-
= ScheduledEventPagination
239-
{ _sepLimit :: !(Maybe Int)
240-
, _sepOffset :: !(Maybe Int)
241-
} deriving (Show, Eq)
242-
243-
parseScheduledEventPagination :: Object -> Parser ScheduledEventPagination
244-
parseScheduledEventPagination o =
245-
ScheduledEventPagination
246-
<$> o .:? "limit"
247-
<*> o .:? "offset"
248-
249-
scheduledEventPaginationToPairs :: ScheduledEventPagination -> [Pair]
250-
scheduledEventPaginationToPairs ScheduledEventPagination{..} =
251-
["limit" .= _sepLimit, "offset" .= _sepOffset]
252-
253-
data GetInvocationsBy
254-
= GIBEventId !EventId !ScheduledEventType
255-
| GIBEvent !ScheduledEvent
256-
deriving (Show, Eq)
257-
258-
data GetEventInvocations
259-
= GetEventInvocations
260-
{ _geiInvocationsBy :: !GetInvocationsBy
261-
, _geiPagination :: !ScheduledEventPagination
262-
} deriving (Eq, Show)
263-
264-
instance FromJSON GetEventInvocations where
265-
parseJSON = withObject "Object" $ \o ->
266-
GetEventInvocations
267-
<$> (parseEventId o <|> (GIBEvent <$> parseScheduledEvent o))
268-
<*> parseScheduledEventPagination o
269-
where
270-
parseEventId o =
271-
GIBEventId <$> o .: "event_id" <*> o .: "type"
272-
273-
instance ToJSON GetEventInvocations where
274-
toJSON GetEventInvocations{..} =
275-
object $ case _geiInvocationsBy of
276-
GIBEventId eventId eventType -> ["event_id" .= eventId, "type" .= eventType]
277-
GIBEvent event -> scheduledEventToPairs event
278-
<> scheduledEventPaginationToPairs _geiPagination
279-
280221
data CronEventSeed
281222
= CronEventSeed
282223
{ cesName :: !TriggerName

0 commit comments

Comments
 (0)