feat: make db-root-spec stable (#2694)
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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] }
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Vendored
+1
-13
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user