feat: make db-root-spec stable (#2694)

This commit is contained in:
Steve Chavez
2023-03-01 12:23:19 -05:00
committed by GitHub
parent 8213c58452
commit 3e99995e6a
8 changed files with 13 additions and 58 deletions
+2
View File
@@ -22,6 +22,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- #1100, Customizable OpenAPI title - @AnthonyFisi - #1100, Customizable OpenAPI title - @AnthonyFisi
- #2506, Add `server-trace-header` for tracing HTTP requests. - @steve-chavez - #2506, Add `server-trace-header` for tracing HTTP requests. - @steve-chavez
+ When the client sends the request header specified in the config it will be included in the response headers. + When the client sends the request header specified in the config it will be included in the response headers.
- #2694, Make `db-root-spec` stable. - @steve-chavez
+ This can be used to override the OpenAPI spec with a custom database function
### Fixed ### Fixed
+6 -8
View File
@@ -111,12 +111,11 @@ postgrest conf appState connWorker =
appConf <- AppState.getConfig appState -- the config must be read again because it can reload appConf <- AppState.getConfig appState -- the config must be read again because it can reload
maybeSchemaCache <- AppState.getSchemaCache appState maybeSchemaCache <- AppState.getSchemaCache appState
pgVer <- AppState.getPgVersion appState pgVer <- AppState.getPgVersion appState
jsonDbS <- AppState.getJsonDbS appState
let let
eitherResponse :: IO (Either Error Wai.Response) eitherResponse :: IO (Either Error Wai.Response)
eitherResponse = eitherResponse =
runExceptT $ postgrestResponse appState appConf maybeSchemaCache jsonDbS pgVer authResult req runExceptT $ postgrestResponse appState appConf maybeSchemaCache pgVer authResult req
response <- either Error.errorResponseFor identity <$> eitherResponse response <- either Error.errorResponseFor identity <$> eitherResponse
-- Launch the connWorker when the connection is down. The postgrest -- Launch the connWorker when the connection is down. The postgrest
@@ -132,12 +131,11 @@ postgrestResponse
:: AppState.AppState :: AppState.AppState
-> AppConfig -> AppConfig
-> Maybe SchemaCache -> Maybe SchemaCache
-> ByteString
-> PgVersion -> PgVersion
-> AuthResult -> AuthResult
-> Wai.Request -> Wai.Request
-> Handler IO Wai.Response -> Handler IO Wai.Response
postgrestResponse appState conf@AppConfig{..} maybeSchemaCache jsonDbS pgVer authResult@AuthResult{..} req = do postgrestResponse appState conf@AppConfig{..} maybeSchemaCache pgVer authResult@AuthResult{..} req = do
sCache <- sCache <-
case maybeSchemaCache of case maybeSchemaCache of
Just sCache -> Just sCache ->
@@ -152,7 +150,7 @@ postgrestResponse appState conf@AppConfig{..} maybeSchemaCache jsonDbS pgVer aut
ApiRequest.userApiRequest conf sCache req body ApiRequest.userApiRequest conf sCache req body
Response.optionalRollback conf apiRequest $ Response.optionalRollback conf apiRequest $
handleRequest authResult conf appState (Just authRole /= configDbAnonRole) configDbPreparedStatements jsonDbS pgVer apiRequest sCache handleRequest authResult conf appState (Just authRole /= configDbAnonRole) configDbPreparedStatements pgVer apiRequest sCache
runDbHandler :: AppState.AppState -> SQL.Mode -> Bool -> Bool -> DbHandler b -> Handler IO b runDbHandler :: AppState.AppState -> SQL.Mode -> Bool -> Bool -> DbHandler b -> Handler IO b
runDbHandler appState mode authenticated prepared handler = do runDbHandler appState mode authenticated prepared handler = do
@@ -170,8 +168,8 @@ runDbHandler appState mode authenticated prepared handler = do
liftEither resp liftEither resp
handleRequest :: AuthResult -> AppConfig -> AppState.AppState -> Bool -> Bool -> ByteString -> PgVersion -> ApiRequest -> SchemaCache -> Handler IO Wai.Response handleRequest :: AuthResult -> AppConfig -> AppState.AppState -> Bool -> Bool -> PgVersion -> ApiRequest -> SchemaCache -> Handler IO Wai.Response
handleRequest AuthResult{..} conf appState authenticated prepared jsonDbS pgVer apiReq@ApiRequest{..} sCache = handleRequest AuthResult{..} conf appState authenticated prepared pgVer apiReq@ApiRequest{..} sCache =
case (iAction, iTarget) of case (iAction, iTarget) of
(ActionRead headersOnly, TargetIdent identifier) -> do (ActionRead headersOnly, TargetIdent identifier) -> do
rPlan <- liftEither $ Plan.readPlan identifier conf sCache apiReq rPlan <- liftEither $ Plan.readPlan identifier conf sCache apiReq
@@ -223,6 +221,6 @@ handleRequest AuthResult{..} conf appState authenticated prepared jsonDbS pgVer
where where
runQuery mode query = runQuery mode query =
runDbHandler appState mode authenticated prepared $ do runDbHandler appState mode authenticated prepared $ do
Query.setPgLocals conf authClaims authRole apiReq jsonDbS pgVer Query.setPgLocals conf authClaims authRole apiReq pgVer
Query.runPreReq conf Query.runPreReq conf
query query
-11
View File
@@ -7,7 +7,6 @@ module PostgREST.AppState
, getConfig , getConfig
, getSchemaCache , getSchemaCache
, getIsListenerOn , getIsListenerOn
, getJsonDbS
, getMainThreadId , getMainThreadId
, getPgVersion , getPgVersion
, getRetryNextIn , getRetryNextIn
@@ -20,7 +19,6 @@ module PostgREST.AppState
, putConfig , putConfig
, putSchemaCache , putSchemaCache
, putIsListenerOn , putIsListenerOn
, putJsonDbS
, putPgVersion , putPgVersion
, putRetryNextIn , putRetryNextIn
, signalListener , signalListener
@@ -58,8 +56,6 @@ data AppState = AppState
, statePgVersion :: IORef PgVersion , statePgVersion :: IORef PgVersion
-- | No schema cache at the start. Will be filled in by the connectionWorker -- | No schema cache at the start. Will be filled in by the connectionWorker
, stateSchemaCache :: IORef (Maybe SchemaCache) , stateSchemaCache :: IORef (Maybe SchemaCache)
-- | Cached SchemaCache in json
, stateJsonDbS :: IORef ByteString
-- | Binary semaphore to make sure just one connectionWorker can run at a time -- | Binary semaphore to make sure just one connectionWorker can run at a time
, stateWorkerSem :: MVar () , stateWorkerSem :: MVar ()
-- | Binary semaphore used to sync the listener(NOTIFY reload) with the connectionWorker. -- | Binary semaphore used to sync the listener(NOTIFY reload) with the connectionWorker.
@@ -90,7 +86,6 @@ initWithPool pool conf = do
appState <- AppState pool appState <- AppState pool
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step <$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
<*> newIORef Nothing <*> newIORef Nothing
<*> newIORef mempty
<*> newEmptyMVar <*> newEmptyMVar
<*> newEmptyMVar <*> newEmptyMVar
<*> newIORef False <*> newIORef False
@@ -146,12 +141,6 @@ getSchemaCache = readIORef . stateSchemaCache
putSchemaCache :: AppState -> Maybe SchemaCache -> IO () putSchemaCache :: AppState -> Maybe SchemaCache -> IO ()
putSchemaCache appState = atomicWriteIORef (stateSchemaCache appState) putSchemaCache appState = atomicWriteIORef (stateSchemaCache appState)
getJsonDbS :: AppState -> IO ByteString
getJsonDbS = readIORef . stateJsonDbS
putJsonDbS :: AppState -> ByteString -> IO ()
putJsonDbS appState = atomicWriteIORef (stateJsonDbS appState)
getWorkerSem :: AppState -> MVar () getWorkerSem :: AppState -> MVar ()
getWorkerSem = stateWorkerSem getWorkerSem = stateWorkerSem
+4 -8
View File
@@ -34,8 +34,7 @@ import qualified PostgREST.SchemaCache.Proc as Proc
import Data.Scientific (FPFormat (..), formatScientific, isInteger) import Data.Scientific (FPFormat (..), formatScientific, isInteger)
import PostgREST.ApiRequest (ApiRequest (..), import PostgREST.ApiRequest (ApiRequest (..))
Target (..))
import PostgREST.ApiRequest.Preferences (PreferCount (..), import PostgREST.ApiRequest.Preferences (PreferCount (..),
PreferParameters (..), PreferParameters (..),
PreferTransaction (..), PreferTransaction (..),
@@ -235,10 +234,10 @@ optionalRollback AppConfig{..} ApiRequest{..} = do
-- | Runs local (transaction scoped) GUCs for every request. -- | Runs local (transaction scoped) GUCs for every request.
setPgLocals :: AppConfig -> KM.KeyMap JSON.Value -> Text -> setPgLocals :: AppConfig -> KM.KeyMap JSON.Value -> Text ->
ApiRequest -> ByteString -> PgVersion -> DbHandler () ApiRequest -> PgVersion -> DbHandler ()
setPgLocals conf claims role req jsonDbS actualPgVersion = lift $ setPgLocals conf claims role req actualPgVersion = lift $
SQL.statement mempty $ SQL.dynamicallyParameterized SQL.statement mempty $ SQL.dynamicallyParameterized
("select " <> intercalateSnippet ", " (searchPathSql : roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql ++ specSql)) ("select " <> intercalateSnippet ", " (searchPathSql : roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql))
HD.noResult (configDbPreparedStatements conf) HD.noResult (configDbPreparedStatements conf)
where where
methodSql = setConfigLocal mempty ("request.method", iMethod req) methodSql = setConfigLocal mempty ("request.method", iMethod req)
@@ -257,9 +256,6 @@ setPgLocals conf claims role req jsonDbS actualPgVersion = lift $
searchPathSql = searchPathSql =
let schemas = pgFmtIdentList (iSchema req : configDbExtraSearchPath conf) in let schemas = pgFmtIdentList (iSchema req : configDbExtraSearchPath conf) in
setConfigLocal mempty ("search_path", schemas) setConfigLocal mempty ("search_path", schemas)
specSql = case iTarget req of
TargetProc{tpIsRootSpec=True} -> [setConfigLocal mempty ("request.spec", jsonDbS)]
_ -> mempty
usesLegacyGucs = configDbUseLegacyGucs conf && actualPgVersion < pgVersion140 usesLegacyGucs = configDbUseLegacyGucs conf && actualPgVersion < pgVersion140
unquoted :: JSON.Value -> Text unquoted :: JSON.Value -> Text
-4
View File
@@ -9,9 +9,7 @@ module PostgREST.Workers
, runAdmin , runAdmin
) where ) where
import qualified Data.Aeson as JSON
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text as T import qualified Data.Text as T
import qualified Hasql.Notifications as SQL import qualified Hasql.Notifications as SQL
import qualified Hasql.Session as SQL import qualified Hasql.Session as SQL
@@ -179,8 +177,6 @@ loadSchemaCache appState = do
Right sCache -> do Right sCache -> do
AppState.putSchemaCache appState (Just sCache) AppState.putSchemaCache appState (Just sCache)
when (isJust configDbRootSpec) .
AppState.putJsonDbS appState . LBS.toStrict $ JSON.encode sCache
AppState.logWithZTime appState "Schema cache loaded" AppState.logWithZTime appState "Schema cache loaded"
return SCLoaded return SCLoaded
-8
View File
@@ -9,8 +9,6 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application) spec :: SpecWith ((), Application)
spec = spec =
describe "root spec function" $ do describe "root spec function" $ do
@@ -22,9 +20,3 @@ spec =
"info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"} "info": {"title": "PostgREST API", "description": "This is a dynamic API generated by PostgREST"}
}|] }|]
{ matchHeaders = ["Content-Type" <:> "application/openapi+json; charset=utf-8"] } { matchHeaders = ["Content-Type" <:> "application/openapi+json; charset=utf-8"] }
it "accepts application/json" $
request methodGet "/"
[("Accept", "application/json")] "" `shouldRespondWith`
200
{ matchHeaders = [matchContentTypeJson] }
-6
View File
@@ -1,6 +1,5 @@
module Main where module Main where
import qualified Data.Aeson as JSON
import qualified Hasql.Pool as P import qualified Hasql.Pool as P
import qualified Hasql.Transaction.Sessions as HT import qualified Hasql.Transaction.Sessions as HT
@@ -14,7 +13,6 @@ import PostgREST.Config (AppConfig (..))
import PostgREST.Config.Database (queryPgVersion) import PostgREST.Config.Database (queryPgVersion)
import PostgREST.SchemaCache (querySchemaCache) import PostgREST.SchemaCache (querySchemaCache)
import Protolude hiding (toList, toS) import Protolude hiding (toList, toS)
import Protolude.Conv (toS)
import SpecHelper import SpecHelper
import qualified PostgREST.AppState as AppState import qualified PostgREST.AppState as AppState
@@ -82,8 +80,6 @@ main = do
appState <- AppState.initWithPool pool config appState <- AppState.initWithPool pool config
AppState.putPgVersion appState actualPgVersion AppState.putPgVersion appState actualPgVersion
AppState.putSchemaCache appState (Just baseSchemaCache) AppState.putSchemaCache appState (Just baseSchemaCache)
when (isJust $ configDbRootSpec config) $
AppState.putJsonDbS appState $ toS $ JSON.encode baseSchemaCache
return ((), postgrest config appState $ pure ()) return ((), postgrest config appState $ pure ())
-- For tests that run with a different SchemaCache(depends on configSchemas) -- For tests that run with a different SchemaCache(depends on configSchemas)
@@ -95,8 +91,6 @@ main = do
appState <- AppState.initWithPool pool config appState <- AppState.initWithPool pool config
AppState.putPgVersion appState actualPgVersion AppState.putPgVersion appState actualPgVersion
AppState.putSchemaCache appState (Just customSchemaCache) AppState.putSchemaCache appState (Just customSchemaCache)
when (isJust $ configDbRootSpec config) $
AppState.putJsonDbS appState $ toS $ JSON.encode baseSchemaCache
return ((), postgrest config appState $ pure ()) return ((), postgrest config appState $ pure ())
let withApp = app testCfg let withApp = app testCfg
+1 -13
View File
@@ -1907,20 +1907,8 @@ openapi json = $$
} }
} }
$$; $$;
accept text;
begin begin
accept = case when current_setting('server_version_num')::int >= 140000 return openapi;
then current_setting('request.headers', true)::json->>'accept'
else current_setting('request.header.accept', true)
end;
case accept
when 'application/openapi+json' then
return openapi;
when 'application/json' then
return (current_setting('request.spec', true)::json)->'dbRelationships'->0->0;
else
return openapi;
end case;
end end
$_$ language plpgsql; $_$ language plpgsql;