Skip to content

Commit

Permalink
Cache the json DbStructure in AppState
Browse files Browse the repository at this point in the history
Also adds a test
  • Loading branch information
steve-chavez committed May 22, 2021
1 parent a91ca48 commit 263b957
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 25 deletions.
8 changes: 5 additions & 3 deletions src/PostgREST/App.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 <-
Expand All @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/PostgREST/AppState.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module PostgREST.AppState
, getConfig
, getDbStructure
, getIsWorkerOn
, getJsonDbS
, getMainThreadId
, getPgVersion
, getPool
Expand All @@ -14,6 +15,7 @@ module PostgREST.AppState
, putConfig
, putDbStructure
, putIsWorkerOn
, putJsonDbS
, putPgVersion
, releasePool
, signalListener
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
7 changes: 3 additions & 4 deletions src/PostgREST/Middleware.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/PostgREST/Workers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion test/Feature/RootSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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] }
15 changes: 11 additions & 4 deletions test/Main.hs
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
Expand Down
16 changes: 3 additions & 13 deletions test/fixtures/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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":{
Expand All @@ -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;
Expand Down

0 comments on commit 263b957

Please sign in to comment.