From 263b9572ad0c38b48fef5aef202aff463172a325 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Fri, 21 May 2021 18:44:32 -0500 Subject: [PATCH] Cache the json DbStructure in AppState Also adds a test --- src/PostgREST/App.hs | 8 +++++--- src/PostgREST/AppState.hs | 11 +++++++++++ src/PostgREST/Middleware.hs | 7 +++---- src/PostgREST/Workers.hs | 3 +++ test/Feature/RootSpec.hs | 6 +++++- test/Main.hs | 15 +++++++++++---- test/fixtures/schema.sql | 16 +++------------- 7 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 6ba5120663f..74ca6168c2c 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -141,11 +141,12 @@ postgrest logLev appState connWorker = conf <- AppState.getConfig appState maybeDbStructure <- AppState.getDbStructure appState pgVer <- AppState.getPgVersion appState + jsonDbS <- AppState.getJsonDbS appState let eitherResponse :: IO (Either Error Wai.Response) eitherResponse = - runExceptT $ postgrestResponse conf maybeDbStructure pgVer (AppState.getPool appState) time req + runExceptT $ postgrestResponse conf maybeDbStructure jsonDbS pgVer (AppState.getPool appState) time req response <- either Error.errorResponseFor identity <$> eitherResponse @@ -159,12 +160,13 @@ postgrest logLev appState connWorker = postgrestResponse :: AppConfig -> Maybe DbStructure + -> ByteString -> PgVersion -> SQL.Pool -> UTCTime -> Wai.Request -> Handler IO Wai.Response -postgrestResponse conf maybeDbStructure pgVer pool time req = do +postgrestResponse conf maybeDbStructure jsonDbS pgVer pool time req = do body <- lift $ Wai.strictRequestBody req dbStructure <- @@ -187,7 +189,7 @@ postgrestResponse conf maybeDbStructure pgVer pool time req = do runDbHandler pool (txMode apiRequest) jwtClaims . Middleware.optionalRollback conf apiRequest $ - Middleware.runPgLocals conf jwtClaims handleReq apiRequest dbStructure + Middleware.runPgLocals conf jwtClaims handleReq apiRequest jsonDbS runDbHandler :: SQL.Pool -> SQL.Mode -> Auth.JWTClaims -> DbHandler a -> Handler IO a runDbHandler pool mode jwtClaims handler = do diff --git a/src/PostgREST/AppState.hs b/src/PostgREST/AppState.hs index 7440d536381..fa021dd2889 100644 --- a/src/PostgREST/AppState.hs +++ b/src/PostgREST/AppState.hs @@ -5,6 +5,7 @@ module PostgREST.AppState , getConfig , getDbStructure , getIsWorkerOn + , getJsonDbS , getMainThreadId , getPgVersion , getPool @@ -14,6 +15,7 @@ module PostgREST.AppState , putConfig , putDbStructure , putIsWorkerOn + , putJsonDbS , putPgVersion , releasePool , signalListener @@ -41,6 +43,8 @@ data AppState = AppState , statePgVersion :: IORef PgVersion -- | No schema cache at the start. Will be filled in by the connectionWorker , stateDbStructure :: IORef (Maybe DbStructure) + -- | Cached DbStructure in json + , stateJsonDbS :: IORef ByteString -- | Helper ref to make sure just one connectionWorker can run at a time , stateIsWorkerOn :: IORef Bool -- | Binary semaphore used to sync the listener(NOTIFY reload) with the connectionWorker. @@ -62,6 +66,7 @@ initWithPool newPool conf = -- assume we're in a supported version when starting, this will be corrected on a later step <$> newIORef minimumPgVersion <*> newIORef Nothing + <*> newIORef mempty <*> newIORef False <*> newEmptyMVar <*> newIORef conf @@ -91,6 +96,12 @@ putDbStructure :: AppState -> DbStructure -> IO () putDbStructure appState structure = atomicWriteIORef (stateDbStructure appState) $ Just structure +getJsonDbS :: AppState -> IO ByteString +getJsonDbS = readIORef . stateJsonDbS + +putJsonDbS :: AppState -> ByteString -> IO () +putJsonDbS appState = atomicWriteIORef (stateJsonDbS appState) + getIsWorkerOn :: AppState -> IO Bool getIsWorkerOn = readIORef . stateIsWorkerOn diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index d8561962055..ee41e58fd69 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -40,7 +40,6 @@ import System.IO.Unsafe (unsafePerformIO) import System.Log.FastLogger (toLogStr) import PostgREST.Config (AppConfig (..), LogLevel (..)) -import PostgREST.DbStructure (DbStructure) import PostgREST.Error (Error, errorResponseFor) import PostgREST.GucHeader (addHeadersIfNotIncluded) import PostgREST.Query.SqlFragment (fromQi, intercalateSnippet, @@ -55,8 +54,8 @@ import Protolude.Conv (toS) -- | Runs local(transaction scoped) GUCs for every request, plus the pre-request function runPgLocals :: AppConfig -> M.HashMap Text JSON.Value -> (ApiRequest -> ExceptT Error H.Transaction Wai.Response) -> - ApiRequest -> DbStructure -> ExceptT Error H.Transaction Wai.Response -runPgLocals conf claims app req dbs = do + ApiRequest -> ByteString -> ExceptT Error H.Transaction Wai.Response +runPgLocals conf claims app req jsonDbS = do lift $ H.statement mempty $ H.dynamicallyParameterized ("select " <> intercalateSnippet ", " (searchPathSql : roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql ++ specSql)) HD.noResult (configDbPreparedStatements conf) @@ -78,7 +77,7 @@ runPgLocals conf claims app req dbs = do setConfigLocal mempty ("search_path", toS schemas) preReqSql = (\f -> "select " <> fromQi f <> "();") <$> configDbPreRequest conf specSql = case iTarget req of - TargetProc{tpIsRootSpec=True} -> [setConfigLocal mempty ("request.spec", toS $ JSON.encode dbs)] + TargetProc{tpIsRootSpec=True} -> [setConfigLocal mempty ("request.spec", jsonDbS)] _ -> mempty -- | Do a pg set_config(setting, value, true) call. This is equivalent to a SET LOCAL. setConfigLocal :: ByteString -> (ByteString, ByteString) -> H.Snippet diff --git a/src/PostgREST/Workers.hs b/src/PostgREST/Workers.hs index 7240efcd95c..cae6e8b375c 100644 --- a/src/PostgREST/Workers.hs +++ b/src/PostgREST/Workers.hs @@ -7,6 +7,7 @@ module PostgREST.Workers , listener ) where +import qualified Data.Aeson as JSON import qualified Data.ByteString as BS import qualified Hasql.Connection as C import qualified Hasql.Notifications as N @@ -172,6 +173,8 @@ loadSchemaCache appState = do Right dbStructure -> do AppState.putDbStructure appState dbStructure + when (isJust configDbRootSpec) $ + AppState.putJsonDbS appState $ toS $ JSON.encode dbStructure putStrLn ("Schema cache loaded" :: Text) return SCLoaded diff --git a/test/Feature/RootSpec.hs b/test/Feature/RootSpec.hs index 92721663b8f..ed0f0da3ab7 100644 --- a/test/Feature/RootSpec.hs +++ b/test/Feature/RootSpec.hs @@ -26,5 +26,9 @@ spec = it "accepts application/json" $ request methodGet "/" [("Accept", "application/json")] "" `shouldRespondWith` - [json| [{"table": "items"}, {"table": "subitems"}] |] + [json| { + "tableName": "orders_view", "tableSchema": "test", + "tableDeletable": true, "tableUpdatable": true, + "tableInsertable": true, "tableDescription": null + } |] { matchHeaders = [matchContentTypeJson] } diff --git a/test/Main.hs b/test/Main.hs index b104cbbb21f..41e13793bce 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -1,5 +1,6 @@ module Main where +import qualified Data.Aeson as JSON import qualified Hasql.Pool as P import qualified Hasql.Transaction.Sessions as HT @@ -68,19 +69,25 @@ main = do let -- For tests that run with the same refDbStructure app cfg = do - appState <- AppState.initWithPool pool $ cfg testDbConn + let config = cfg testDbConn + appState <- AppState.initWithPool pool config AppState.putPgVersion appState actualPgVersion AppState.putDbStructure appState baseDbStructure + when (isJust $ configDbRootSpec config) $ + AppState.putJsonDbS appState $ toS $ JSON.encode baseDbStructure return ((), postgrest LogCrit appState $ pure ()) -- For tests that run with a different DbStructure(depends on configSchemas) appDbs cfg = do + let config = cfg testDbConn customDbStructure <- loadDbStructure pool - (configDbSchemas $ cfg testDbConn) - (configDbExtraSearchPath $ cfg testDbConn) - appState <- AppState.initWithPool pool $ cfg testDbConn + (configDbSchemas config) + (configDbExtraSearchPath config) + appState <- AppState.initWithPool pool config AppState.putDbStructure appState customDbStructure + when (isJust $ configDbRootSpec config) $ + AppState.putJsonDbS appState $ toS $ JSON.encode baseDbStructure return ((), postgrest LogCrit appState $ pure ()) let withApp = app testCfg diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index a703af47689..eabdea679cc 100644 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -1713,9 +1713,9 @@ returns integer as $$ select a + b; $$ language sql; -create function root() returns jsonb as $_$ +create or replace function root() returns json as $_$ declare -openapi jsonb = $$ +openapi json = $$ { "swagger": "2.0", "info":{ @@ -1724,22 +1724,12 @@ openapi jsonb = $$ } } $$; -simple jsonb = $$ - [ - { - "table":"items" - }, - { - "table":"subitems" - } - ] -$$; begin case current_setting('request.header.accept', true) when 'application/openapi+json' then return openapi; when 'application/json' then - return simple; + return (current_setting('request.spec', true)::json)->'dbRelationships'->0->'relTable'; else return openapi; end case;