fix: resource embedding opens empty transaction
This required untangling logic from App.hs. Building/rejecting a plan no longer consumes a pool connection. * Added io test for failed resource embedding not consuming connection * optionalrollback to Query.hs and Response.hs * delete Middleware module * remove plan logic from App.hs * remove RequestContext * remove pkCols logic from App.hs * remove proc logic from App.hs * remove config logic from handleRequest
This commit is contained in:
committed by
Steve Chavez
parent
d37e14c4db
commit
e4b98d51be
@@ -53,7 +53,6 @@ library
|
|||||||
PostgREST.Error
|
PostgREST.Error
|
||||||
PostgREST.GucHeader
|
PostgREST.GucHeader
|
||||||
PostgREST.Logger
|
PostgREST.Logger
|
||||||
PostgREST.Middleware
|
|
||||||
PostgREST.MediaType
|
PostgREST.MediaType
|
||||||
PostgREST.Query
|
PostgREST.Query
|
||||||
PostgREST.Query.QueryBuilder
|
PostgREST.Query.QueryBuilder
|
||||||
|
|||||||
+58
-132
@@ -26,7 +26,6 @@ import Network.Wai.Handler.Warp (defaultSettings, setHost, setPort,
|
|||||||
setServerName)
|
setServerName)
|
||||||
import System.Posix.Types (FileMode)
|
import System.Posix.Types (FileMode)
|
||||||
|
|
||||||
import qualified Data.HashMap.Strict as HM
|
|
||||||
import qualified Hasql.Transaction.Sessions as SQL
|
import qualified Hasql.Transaction.Sessions as SQL
|
||||||
import qualified Network.Wai as Wai
|
import qualified Network.Wai as Wai
|
||||||
import qualified Network.Wai.Handler.Warp as Warp
|
import qualified Network.Wai.Handler.Warp as Warp
|
||||||
@@ -37,47 +36,26 @@ import qualified PostgREST.Auth as Auth
|
|||||||
import qualified PostgREST.Cors as Cors
|
import qualified PostgREST.Cors as Cors
|
||||||
import qualified PostgREST.Error as Error
|
import qualified PostgREST.Error as Error
|
||||||
import qualified PostgREST.Logger as Logger
|
import qualified PostgREST.Logger as Logger
|
||||||
import qualified PostgREST.Middleware as Middleware
|
|
||||||
import qualified PostgREST.Plan as Plan
|
import qualified PostgREST.Plan as Plan
|
||||||
import qualified PostgREST.Query as Query
|
import qualified PostgREST.Query as Query
|
||||||
import qualified PostgREST.Request.ApiRequest as ApiRequest
|
import qualified PostgREST.Request.ApiRequest as ApiRequest
|
||||||
import qualified PostgREST.Request.Types as ApiRequestTypes
|
import qualified PostgREST.Request.Types as ApiRequestTypes
|
||||||
import qualified PostgREST.Response as Response
|
import qualified PostgREST.Response as Response
|
||||||
|
|
||||||
import PostgREST.AppState (AppState)
|
import PostgREST.AppState (AppState)
|
||||||
import PostgREST.Auth (AuthResult (..))
|
import PostgREST.Auth (AuthResult (..))
|
||||||
import PostgREST.Config (AppConfig (..),
|
import PostgREST.Config (AppConfig (..), LogLevel (..))
|
||||||
LogLevel (..))
|
import PostgREST.Config.PgVersion (PgVersion (..))
|
||||||
import PostgREST.Config.PgVersion (PgVersion (..))
|
import PostgREST.DbStructure (DbStructure (..))
|
||||||
import PostgREST.DbStructure (DbStructure (..))
|
import PostgREST.Error (Error)
|
||||||
import PostgREST.DbStructure.Identifiers (FieldName,
|
import PostgREST.Query (DbHandler)
|
||||||
QualifiedIdentifier (..),
|
import PostgREST.Request.ApiRequest (Action (..), ApiRequest (..),
|
||||||
Schema)
|
Mutation (..), Target (..))
|
||||||
import PostgREST.DbStructure.Proc (ProcDescription (..))
|
import PostgREST.Version (prettyVersion)
|
||||||
import PostgREST.DbStructure.Table (Table (..))
|
import PostgREST.Workers (connectionWorker, listener)
|
||||||
import PostgREST.Error (Error)
|
|
||||||
import PostgREST.Plan.MutatePlan (MutatePlan)
|
|
||||||
import PostgREST.Plan.ReadPlan (ReadPlanTree)
|
|
||||||
import PostgREST.Query (DbHandler)
|
|
||||||
import PostgREST.Request.ApiRequest (Action (..),
|
|
||||||
ApiRequest (..),
|
|
||||||
InvokeMethod (..),
|
|
||||||
Mutation (..), Target (..))
|
|
||||||
import PostgREST.Request.Preferences (PreferRepresentation (..))
|
|
||||||
import PostgREST.Version (prettyVersion)
|
|
||||||
import PostgREST.Workers (connectionWorker, listener)
|
|
||||||
|
|
||||||
import qualified PostgREST.DbStructure.Proc as Proc
|
|
||||||
|
|
||||||
import Protolude hiding (Handler)
|
import Protolude hiding (Handler)
|
||||||
|
|
||||||
data RequestContext = RequestContext
|
|
||||||
{ ctxConfig :: AppConfig
|
|
||||||
, ctxDbStructure :: DbStructure
|
|
||||||
, ctxApiRequest :: ApiRequest
|
|
||||||
, ctxPgVersion :: PgVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
type Handler = ExceptT Error
|
type Handler = ExceptT Error
|
||||||
|
|
||||||
type SignalHandlerInstaller = AppState -> IO()
|
type SignalHandlerInstaller = AppState -> IO()
|
||||||
@@ -163,7 +141,7 @@ postgrestResponse
|
|||||||
-> AuthResult
|
-> AuthResult
|
||||||
-> Wai.Request
|
-> Wai.Request
|
||||||
-> Handler IO Wai.Response
|
-> Handler IO Wai.Response
|
||||||
postgrestResponse appState conf@AppConfig{..} maybeDbStructure jsonDbS pgVer AuthResult{..} req = do
|
postgrestResponse appState conf@AppConfig{..} maybeDbStructure jsonDbS pgVer authResult@AuthResult{..} req = do
|
||||||
dbStructure <-
|
dbStructure <-
|
||||||
case maybeDbStructure of
|
case maybeDbStructure of
|
||||||
Just dbStructure ->
|
Just dbStructure ->
|
||||||
@@ -177,15 +155,8 @@ postgrestResponse appState conf@AppConfig{..} maybeDbStructure jsonDbS pgVer Aut
|
|||||||
liftEither . mapLeft Error.ApiRequestError $
|
liftEither . mapLeft Error.ApiRequestError $
|
||||||
ApiRequest.userApiRequest conf dbStructure req body
|
ApiRequest.userApiRequest conf dbStructure req body
|
||||||
|
|
||||||
let ctx apiReq = RequestContext conf dbStructure apiReq pgVer
|
Response.optionalRollback conf apiRequest $
|
||||||
|
handleRequest authResult conf appState (Query.txMode apiRequest) (Just authRole /= configDbAnonRole) configDbPreparedStatements jsonDbS pgVer apiRequest dbStructure
|
||||||
if iAction apiRequest == ActionInfo then
|
|
||||||
pure $ Response.infoResponse (iTarget apiRequest) dbStructure
|
|
||||||
else
|
|
||||||
runDbHandler appState (Query.txMode apiRequest) (Just authRole /= configDbAnonRole) configDbPreparedStatements .
|
|
||||||
Middleware.optionalRollback conf apiRequest $ do
|
|
||||||
Query.setPgLocals conf authClaims authRole apiRequest jsonDbS pgVer
|
|
||||||
handleRequest (ctx apiRequest)
|
|
||||||
|
|
||||||
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
|
||||||
@@ -199,97 +170,52 @@ runDbHandler appState mode authenticated prepared handler = do
|
|||||||
|
|
||||||
liftEither resp
|
liftEither resp
|
||||||
|
|
||||||
handleRequest :: RequestContext -> DbHandler Wai.Response
|
handleRequest :: AuthResult -> AppConfig -> AppState.AppState -> SQL.Mode -> Bool -> Bool -> ByteString -> PgVersion -> ApiRequest -> DbStructure -> Handler IO Wai.Response
|
||||||
handleRequest context@(RequestContext _ _ ApiRequest{..} _) =
|
handleRequest AuthResult{..} conf appState mode authenticated prepared jsonDbS pgVer apiReq@ApiRequest{..} dbStructure =
|
||||||
case (iAction, iTarget) of
|
case (iAction, iTarget) of
|
||||||
(ActionRead headersOnly, TargetIdent identifier) ->
|
(ActionRead headersOnly, TargetIdent identifier) -> do
|
||||||
handleRead headersOnly identifier context
|
rPlan <- liftEither $ Plan.readPlan identifier conf dbStructure apiReq
|
||||||
(ActionMutate MutationCreate, TargetIdent identifier) ->
|
resultSet <- runQuery $ Query.readQuery rPlan conf apiReq
|
||||||
handleCreate identifier context
|
return $ Response.readResponse headersOnly identifier apiReq resultSet
|
||||||
(ActionMutate MutationUpdate, TargetIdent identifier) ->
|
|
||||||
handleUpdate identifier context
|
(ActionMutate MutationCreate, TargetIdent identifier) -> do
|
||||||
(ActionMutate MutationSingleUpsert, TargetIdent identifier) ->
|
mrPlan <- liftEither $ Plan.mutateReadPlan MutationCreate apiReq identifier conf dbStructure
|
||||||
handleSingleUpsert identifier context
|
resultSet <- runQuery $ Query.createQuery mrPlan apiReq conf
|
||||||
(ActionMutate MutationDelete, TargetIdent identifier) ->
|
return $ Response.createResponse identifier mrPlan apiReq resultSet
|
||||||
handleDelete identifier context
|
|
||||||
(ActionInvoke invMethod, TargetProc proc _) ->
|
(ActionMutate MutationUpdate, TargetIdent identifier) -> do
|
||||||
handleInvoke invMethod proc context
|
mrPlan <- liftEither $ Plan.mutateReadPlan MutationUpdate apiReq identifier conf dbStructure
|
||||||
(ActionInspect headersOnly, TargetDefaultSpec tSchema) ->
|
resultSet <- runQuery $ Query.updateQuery mrPlan apiReq conf
|
||||||
handleOpenApi headersOnly tSchema context
|
return $ Response.updateResponse apiReq resultSet
|
||||||
|
|
||||||
|
(ActionMutate MutationSingleUpsert, TargetIdent identifier) -> do
|
||||||
|
mrPlan <- liftEither $ Plan.mutateReadPlan MutationSingleUpsert apiReq identifier conf dbStructure
|
||||||
|
resultSet <- runQuery $ Query.singleUpsertQuery mrPlan apiReq conf
|
||||||
|
return $ Response.singleUpsertResponse apiReq resultSet
|
||||||
|
|
||||||
|
(ActionMutate MutationDelete, TargetIdent identifier) -> do
|
||||||
|
mrPlan <- liftEither $ Plan.mutateReadPlan MutationDelete apiReq identifier conf dbStructure
|
||||||
|
resultSet <- runQuery $ Query.deleteQuery mrPlan apiReq conf
|
||||||
|
return $ Response.deleteResponse apiReq resultSet
|
||||||
|
|
||||||
|
(ActionInvoke invMethod, TargetProc proc _) -> do
|
||||||
|
cPlan <- liftEither $ Plan.callReadPlan proc conf dbStructure apiReq
|
||||||
|
resultSet <- runQuery $ Query.invokeQuery proc cPlan apiReq conf
|
||||||
|
return $ Response.invokeResponse invMethod proc apiReq resultSet
|
||||||
|
|
||||||
|
(ActionInspect headersOnly, TargetDefaultSpec tSchema) -> do
|
||||||
|
oaiResult <- runQuery $ Query.openApiQuery dbStructure pgVer conf tSchema
|
||||||
|
return $ Response.openApiResponse headersOnly oaiResult conf dbStructure iSchema iNegotiatedByProfile
|
||||||
|
|
||||||
|
(ActionInfo, _) ->
|
||||||
|
return $ Response.infoResponse iTarget dbStructure
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
-- This is unreachable as the ApiRequest.hs rejects it before
|
-- This is unreachable as the ApiRequest.hs rejects it before
|
||||||
-- TODO Refactor the Action/Target types to remove this line
|
-- TODO Refactor the Action/Target types to remove this line
|
||||||
throwError $ Error.ApiRequestError ApiRequestTypes.NotFound
|
throwError $ Error.ApiRequestError ApiRequestTypes.NotFound
|
||||||
|
where
|
||||||
handleRead :: Bool -> QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response
|
runQuery query =
|
||||||
handleRead headersOnly identifier context@RequestContext{..} = do
|
runDbHandler appState mode authenticated prepared $ do
|
||||||
req <- liftEither $ readPlan identifier context
|
Query.setPgLocals conf authClaims authRole apiReq jsonDbS pgVer
|
||||||
|
query
|
||||||
resultSet <- Query.readQuery req ctxConfig ctxApiRequest
|
|
||||||
|
|
||||||
pure $ Response.readResponse headersOnly identifier ctxApiRequest resultSet
|
|
||||||
|
|
||||||
handleCreate :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response
|
|
||||||
handleCreate identifier context@RequestContext{..} = do
|
|
||||||
let
|
|
||||||
ApiRequest{..} = ctxApiRequest
|
|
||||||
pkCols = if iPreferRepresentation /= None || isJust iPreferResolution
|
|
||||||
then maybe mempty tablePKCols $ HM.lookup identifier $ dbTables ctxDbStructure
|
|
||||||
else mempty
|
|
||||||
|
|
||||||
(mutateReq, readReq) <- liftEither $ mutatePlan MutationCreate identifier context pkCols
|
|
||||||
|
|
||||||
resultSet <- Query.createQuery mutateReq readReq pkCols ctxApiRequest ctxConfig
|
|
||||||
|
|
||||||
pure $ Response.createResponse identifier pkCols ctxApiRequest resultSet
|
|
||||||
|
|
||||||
handleUpdate :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response
|
|
||||||
handleUpdate identifier context@(RequestContext ctxConfig _ ctxApiRequest _) = do
|
|
||||||
(mutateReq, readReq) <- liftEither $ mutatePlan MutationUpdate identifier context mempty
|
|
||||||
resultSet <- Query.updateQuery mutateReq readReq ctxApiRequest ctxConfig
|
|
||||||
pure $ Response.updateResponse ctxApiRequest resultSet
|
|
||||||
|
|
||||||
handleSingleUpsert :: QualifiedIdentifier -> RequestContext-> DbHandler Wai.Response
|
|
||||||
handleSingleUpsert identifier context@(RequestContext ctxConfig ctxDbStructure ctxApiRequest _) = do
|
|
||||||
let pkCols = maybe mempty tablePKCols $ HM.lookup identifier $ dbTables ctxDbStructure
|
|
||||||
(mutateReq, readReq) <- liftEither $ mutatePlan MutationSingleUpsert identifier context pkCols
|
|
||||||
resultSet <- Query.singleUpsertQuery mutateReq readReq ctxApiRequest ctxConfig
|
|
||||||
pure $ Response.singleUpsertResponse ctxApiRequest resultSet
|
|
||||||
|
|
||||||
handleDelete :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response
|
|
||||||
handleDelete identifier context@(RequestContext ctxConfig _ ctxApiRequest _) = do
|
|
||||||
(mutateReq, readReq) <- liftEither $ mutatePlan MutationDelete identifier context mempty
|
|
||||||
resultSet <- Query.deleteQuery mutateReq readReq ctxApiRequest ctxConfig
|
|
||||||
pure $ Response.deleteResponse ctxApiRequest resultSet
|
|
||||||
|
|
||||||
handleInvoke :: InvokeMethod -> ProcDescription -> RequestContext -> DbHandler Wai.Response
|
|
||||||
handleInvoke invMethod proc context@RequestContext{..} = do
|
|
||||||
let
|
|
||||||
identifier =
|
|
||||||
QualifiedIdentifier
|
|
||||||
(pdSchema proc)
|
|
||||||
(fromMaybe (pdName proc) $ Proc.procTableName proc)
|
|
||||||
|
|
||||||
readReq <- liftEither $ readPlan identifier context
|
|
||||||
let callReq = Plan.callPlan proc ctxApiRequest readReq
|
|
||||||
|
|
||||||
resultSet <- Query.invokeQuery proc callReq readReq ctxApiRequest ctxConfig
|
|
||||||
|
|
||||||
pure $ Response.invokeResponse invMethod proc ctxApiRequest resultSet
|
|
||||||
|
|
||||||
handleOpenApi :: Bool -> Schema -> RequestContext -> DbHandler Wai.Response
|
|
||||||
handleOpenApi headersOnly tSchema (RequestContext conf dbStructure apiRequest pgVer) = do
|
|
||||||
oaiResult <- Query.openApiQuery dbStructure pgVer conf tSchema
|
|
||||||
pure $ Response.openApiResponse headersOnly oaiResult conf dbStructure (iSchema apiRequest) (iNegotiatedByProfile apiRequest)
|
|
||||||
|
|
||||||
mutatePlan :: Mutation -> QualifiedIdentifier -> RequestContext -> [FieldName] -> Either Error (MutatePlan, ReadPlanTree)
|
|
||||||
mutatePlan mutation identifier@QualifiedIdentifier{..} context@RequestContext{..} pkCols = do
|
|
||||||
readReq <- readPlan identifier context
|
|
||||||
mutateReq <- Plan.mutatePlan mutation qiSchema qiName ctxApiRequest pkCols readReq
|
|
||||||
pure (mutateReq, readReq)
|
|
||||||
|
|
||||||
readPlan :: QualifiedIdentifier -> RequestContext -> Either Error ReadPlanTree
|
|
||||||
readPlan QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure apiRequest _) =
|
|
||||||
Plan.readPlan qiSchema qiName configDbMaxRows
|
|
||||||
(dbRelationships dbStructure)
|
|
||||||
apiRequest
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
{-|
|
|
||||||
Module : PostgREST.Middleware
|
|
||||||
Description : Sets CORS policy. Also the PostgreSQL GUCs, role, search_path and pre-request function.
|
|
||||||
-}
|
|
||||||
{-# LANGUAGE BlockArguments #-}
|
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
|
||||||
module PostgREST.Middleware
|
|
||||||
( optionalRollback
|
|
||||||
) where
|
|
||||||
|
|
||||||
import qualified Hasql.Transaction as SQL
|
|
||||||
import qualified Network.Wai as Wai
|
|
||||||
|
|
||||||
|
|
||||||
import PostgREST.Config (AppConfig (..))
|
|
||||||
import PostgREST.Error (Error, errorResponseFor)
|
|
||||||
import PostgREST.GucHeader (addHeadersIfNotIncluded)
|
|
||||||
import PostgREST.Request.ApiRequest (ApiRequest (..))
|
|
||||||
|
|
||||||
import PostgREST.Request.Preferences
|
|
||||||
|
|
||||||
import Protolude
|
|
||||||
|
|
||||||
-- | Set a transaction to eventually roll back if requested and set respective
|
|
||||||
-- headers on the response.
|
|
||||||
optionalRollback
|
|
||||||
:: AppConfig
|
|
||||||
-> ApiRequest
|
|
||||||
-> ExceptT Error SQL.Transaction Wai.Response
|
|
||||||
-> ExceptT Error SQL.Transaction Wai.Response
|
|
||||||
optionalRollback AppConfig{..} ApiRequest{..} transaction = do
|
|
||||||
resp <- catchError transaction $ return . errorResponseFor
|
|
||||||
when (shouldRollback || (configDbTxRollbackAll && not shouldCommit)) $ lift do
|
|
||||||
SQL.sql "SET CONSTRAINTS ALL IMMEDIATE"
|
|
||||||
SQL.condemn
|
|
||||||
return $ Wai.mapResponseHeaders preferenceApplied resp
|
|
||||||
where
|
|
||||||
shouldCommit =
|
|
||||||
configDbTxAllowOverride && iPreferTransaction == Just Commit
|
|
||||||
shouldRollback =
|
|
||||||
configDbTxAllowOverride && iPreferTransaction == Just Rollback
|
|
||||||
preferenceApplied
|
|
||||||
| shouldCommit =
|
|
||||||
addHeadersIfNotIncluded
|
|
||||||
[toAppliedHeader Commit]
|
|
||||||
| shouldRollback =
|
|
||||||
addHeadersIfNotIncluded
|
|
||||||
[toAppliedHeader Rollback]
|
|
||||||
| otherwise =
|
|
||||||
identity
|
|
||||||
+47
-17
@@ -17,20 +17,25 @@ resource.
|
|||||||
|
|
||||||
module PostgREST.Plan
|
module PostgREST.Plan
|
||||||
( readPlan
|
( readPlan
|
||||||
, mutatePlan
|
, mutateReadPlan
|
||||||
, callPlan
|
, callReadPlan
|
||||||
|
, MutateReadPlan(..)
|
||||||
|
, CallReadPlan(..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.HashMap.Strict as HM
|
import qualified Data.HashMap.Strict as HM
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
|
import qualified PostgREST.DbStructure.Proc as Proc
|
||||||
|
|
||||||
import Data.Either.Combinators (mapLeft)
|
import Data.Either.Combinators (mapLeft)
|
||||||
import Data.List (delete)
|
import Data.List (delete)
|
||||||
import Data.Tree (Tree (..))
|
import Data.Tree (Tree (..))
|
||||||
|
|
||||||
|
import PostgREST.Config (AppConfig (..))
|
||||||
|
import PostgREST.DbStructure (DbStructure (..))
|
||||||
import PostgREST.DbStructure.Identifiers (FieldName,
|
import PostgREST.DbStructure.Identifiers (FieldName,
|
||||||
QualifiedIdentifier (..),
|
QualifiedIdentifier (..),
|
||||||
Schema, TableName)
|
Schema)
|
||||||
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
||||||
ProcParam (..),
|
ProcParam (..),
|
||||||
procReturnsScalar)
|
procReturnsScalar)
|
||||||
@@ -38,6 +43,7 @@ import PostgREST.DbStructure.Relationship (Cardinality (..),
|
|||||||
Junction (..),
|
Junction (..),
|
||||||
Relationship (..),
|
Relationship (..),
|
||||||
RelationshipsMap)
|
RelationshipsMap)
|
||||||
|
import PostgREST.DbStructure.Table (tablePKCols)
|
||||||
import PostgREST.Error (Error (..))
|
import PostgREST.Error (Error (..))
|
||||||
import PostgREST.Query.SqlFragment (sourceCTEName)
|
import PostgREST.Query.SqlFragment (sourceCTEName)
|
||||||
import PostgREST.RangeQuery (NonnegRange, allRange,
|
import PostgREST.RangeQuery (NonnegRange, allRange,
|
||||||
@@ -50,7 +56,8 @@ import PostgREST.Request.ApiRequest (Action (..),
|
|||||||
|
|
||||||
import PostgREST.Plan.CallPlan
|
import PostgREST.Plan.CallPlan
|
||||||
import PostgREST.Plan.MutatePlan
|
import PostgREST.Plan.MutatePlan
|
||||||
import PostgREST.Plan.ReadPlan as ReadPlan
|
import PostgREST.Plan.ReadPlan as ReadPlan
|
||||||
|
|
||||||
import PostgREST.Request.Preferences
|
import PostgREST.Request.Preferences
|
||||||
import PostgREST.Request.Types
|
import PostgREST.Request.Types
|
||||||
|
|
||||||
@@ -58,14 +65,37 @@ import qualified PostgREST.Request.QueryParams as QueryParams
|
|||||||
|
|
||||||
import Protolude hiding (from)
|
import Protolude hiding (from)
|
||||||
|
|
||||||
|
data MutateReadPlan = MutateReadPlan {
|
||||||
|
mrReadPlan :: ReadPlanTree
|
||||||
|
, mrMutatePlan :: MutatePlan
|
||||||
|
}
|
||||||
|
|
||||||
|
data CallReadPlan = CallReadPlan {
|
||||||
|
crReadPlan :: ReadPlanTree
|
||||||
|
, crCallPlan :: CallPlan
|
||||||
|
}
|
||||||
|
|
||||||
|
mutateReadPlan :: Mutation -> ApiRequest -> QualifiedIdentifier -> AppConfig -> DbStructure -> Either Error MutateReadPlan
|
||||||
|
mutateReadPlan mutation apiRequest identifier conf dbStructure = do
|
||||||
|
rPlan <- readPlan identifier conf dbStructure apiRequest
|
||||||
|
mPlan <- mutatePlan mutation identifier apiRequest dbStructure rPlan
|
||||||
|
return $ MutateReadPlan rPlan mPlan
|
||||||
|
|
||||||
|
callReadPlan :: ProcDescription -> AppConfig -> DbStructure -> ApiRequest -> Either Error CallReadPlan
|
||||||
|
callReadPlan proc conf dbStructure apiRequest = do
|
||||||
|
let identifier = QualifiedIdentifier (pdSchema proc) (fromMaybe (pdName proc) $ Proc.procTableName proc)
|
||||||
|
rPlan <- readPlan identifier conf dbStructure apiRequest
|
||||||
|
let cPlan = callPlan proc apiRequest rPlan
|
||||||
|
return $ CallReadPlan rPlan cPlan
|
||||||
|
|
||||||
-- | Builds the ReadPlan tree on a number of stages.
|
-- | Builds the ReadPlan tree on a number of stages.
|
||||||
-- | Adds filters, order, limits on its respective nodes.
|
-- | Adds filters, order, limits on its respective nodes.
|
||||||
-- | Adds joins conditions obtained from resource embedding.
|
-- | Adds joins conditions obtained from resource embedding.
|
||||||
readPlan :: Schema -> TableName -> Maybe Integer -> RelationshipsMap -> ApiRequest -> Either Error ReadPlanTree
|
readPlan :: QualifiedIdentifier -> AppConfig -> DbStructure -> ApiRequest -> Either Error ReadPlanTree
|
||||||
readPlan schema rootTableName maxRows allRels apiRequest =
|
readPlan qi@QualifiedIdentifier{..} AppConfig{configDbMaxRows} DbStructure{dbRelationships} apiRequest =
|
||||||
mapLeft ApiRequestError $
|
mapLeft ApiRequestError $
|
||||||
treeRestrictRange maxRows (iAction apiRequest) =<<
|
treeRestrictRange configDbMaxRows (iAction apiRequest) =<<
|
||||||
augmentRequestWithJoin schema allRels =<<
|
augmentRequestWithJoin qiSchema dbRelationships =<<
|
||||||
addLogicTrees apiRequest =<<
|
addLogicTrees apiRequest =<<
|
||||||
addRanges apiRequest =<<
|
addRanges apiRequest =<<
|
||||||
addOrders apiRequest =<<
|
addOrders apiRequest =<<
|
||||||
@@ -73,9 +103,9 @@ readPlan schema rootTableName maxRows allRels apiRequest =
|
|||||||
where
|
where
|
||||||
QueryParams.QueryParams{..} = iQueryParams apiRequest
|
QueryParams.QueryParams{..} = iQueryParams apiRequest
|
||||||
(rootName, rootAlias) = case iAction apiRequest of
|
(rootName, rootAlias) = case iAction apiRequest of
|
||||||
ActionRead _ -> (QualifiedIdentifier schema rootTableName, Nothing)
|
ActionRead _ -> (qi, Nothing)
|
||||||
-- the CTE we use for non-read cases has a sourceCTEName(see Statements.hs) as the WITH name so we use the table name as an alias so findRel can find the right relationship
|
-- the CTE we use for non-read cases has a sourceCTEName(see Statements.hs) as the WITH name so we use the table name as an alias so findRel can find the right relationship
|
||||||
_ -> (QualifiedIdentifier mempty $ decodeUtf8 sourceCTEName, Just rootTableName)
|
_ -> (QualifiedIdentifier mempty $ decodeUtf8 sourceCTEName, Just qiName)
|
||||||
|
|
||||||
-- Build the initial tree with a Depth attribute so when a self join occurs we
|
-- Build the initial tree with a Depth attribute so when a self join occurs we
|
||||||
-- can differentiate the parent and child tables by having an alias like
|
-- can differentiate the parent and child tables by having an alias like
|
||||||
@@ -305,11 +335,11 @@ updateNode f (targetNodeName:remainingPath, a) (Right (Node rootNode forest)) =
|
|||||||
findNode :: Maybe ReadPlanTree
|
findNode :: Maybe ReadPlanTree
|
||||||
findNode = find (\(Node ReadPlan{nodeName, nodeAlias} _) -> nodeName == targetNodeName || nodeAlias == Just targetNodeName) forest
|
findNode = find (\(Node ReadPlan{nodeName, nodeAlias} _) -> nodeName == targetNodeName || nodeAlias == Just targetNodeName) forest
|
||||||
|
|
||||||
mutatePlan :: Mutation -> Schema -> TableName -> ApiRequest -> [FieldName] -> ReadPlanTree -> Either Error MutatePlan
|
mutatePlan :: Mutation -> QualifiedIdentifier -> ApiRequest -> DbStructure -> ReadPlanTree -> Either Error MutatePlan
|
||||||
mutatePlan mutation schema tName ApiRequest{..} pkCols readReq = mapLeft ApiRequestError $
|
mutatePlan mutation qi ApiRequest{..} dbStructure readReq = mapLeft ApiRequestError $
|
||||||
case mutation of
|
case mutation of
|
||||||
MutationCreate ->
|
MutationCreate ->
|
||||||
Right $ Insert qi iColumns body ((,) <$> iPreferResolution <*> Just confCols) [] returnings
|
Right $ Insert qi iColumns body ((,) <$> iPreferResolution <*> Just confCols) [] returnings pkCols
|
||||||
MutationUpdate -> Right $ Update qi iColumns body combinedLogic iTopLevelRange rootOrder returnings
|
MutationUpdate -> Right $ Update qi iColumns body combinedLogic iTopLevelRange rootOrder returnings
|
||||||
MutationSingleUpsert ->
|
MutationSingleUpsert ->
|
||||||
if null qsLogic &&
|
if null qsLogic &&
|
||||||
@@ -318,18 +348,18 @@ mutatePlan mutation schema tName ApiRequest{..} pkCols readReq = mapLeft ApiRequ
|
|||||||
all (\case
|
all (\case
|
||||||
Filter _ (OpExpr False (Op OpEqual _)) -> True
|
Filter _ (OpExpr False (Op OpEqual _)) -> True
|
||||||
_ -> False) qsFiltersRoot
|
_ -> False) qsFiltersRoot
|
||||||
then Right $ Insert qi iColumns body (Just (MergeDuplicates, pkCols)) combinedLogic returnings
|
then Right $ Insert qi iColumns body (Just (MergeDuplicates, pkCols)) combinedLogic returnings mempty
|
||||||
else
|
else
|
||||||
Left InvalidFilters
|
Left InvalidFilters
|
||||||
MutationDelete -> Right $ Delete qi combinedLogic iTopLevelRange rootOrder returnings
|
MutationDelete -> Right $ Delete qi combinedLogic iTopLevelRange rootOrder returnings
|
||||||
where
|
where
|
||||||
confCols = fromMaybe pkCols qsOnConflict
|
confCols = fromMaybe pkCols qsOnConflict
|
||||||
QueryParams.QueryParams{..} = iQueryParams
|
QueryParams.QueryParams{..} = iQueryParams
|
||||||
qi = QualifiedIdentifier schema tName
|
|
||||||
returnings =
|
returnings =
|
||||||
if iPreferRepresentation == None
|
if iPreferRepresentation == None
|
||||||
then []
|
then []
|
||||||
else returningCols readReq pkCols
|
else returningCols readReq pkCols
|
||||||
|
pkCols = maybe mempty tablePKCols $ HM.lookup qi $ dbTables dbStructure
|
||||||
logic = map snd qsLogic
|
logic = map snd qsLogic
|
||||||
rootOrder = maybe [] snd $ find (\(x, _) -> null x) qsOrder
|
rootOrder = maybe [] snd $ find (\(x, _) -> null x) qsOrder
|
||||||
combinedLogic = foldr addFilterToLogicForest logic qsFiltersRoot
|
combinedLogic = foldr addFilterToLogicForest logic qsFiltersRoot
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ data MutatePlan
|
|||||||
, onConflict :: Maybe (PreferResolution, [FieldName])
|
, onConflict :: Maybe (PreferResolution, [FieldName])
|
||||||
, where_ :: [LogicTree]
|
, where_ :: [LogicTree]
|
||||||
, returning :: [FieldName]
|
, returning :: [FieldName]
|
||||||
|
, insPkCols :: [FieldName]
|
||||||
}
|
}
|
||||||
| Update
|
| Update
|
||||||
{ in_ :: QualifiedIdentifier
|
{ in_ :: QualifiedIdentifier
|
||||||
|
|||||||
+48
-25
@@ -1,3 +1,4 @@
|
|||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
module PostgREST.Query
|
module PostgREST.Query
|
||||||
( createQuery
|
( createQuery
|
||||||
@@ -38,8 +39,7 @@ import PostgREST.Config (AppConfig (..),
|
|||||||
import PostgREST.Config.PgVersion (PgVersion (..),
|
import PostgREST.Config.PgVersion (PgVersion (..),
|
||||||
pgVersion140)
|
pgVersion140)
|
||||||
import PostgREST.DbStructure (DbStructure (..))
|
import PostgREST.DbStructure (DbStructure (..))
|
||||||
import PostgREST.DbStructure.Identifiers (FieldName,
|
import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..),
|
||||||
QualifiedIdentifier (..),
|
|
||||||
Schema)
|
Schema)
|
||||||
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
||||||
ProcVolatility (..),
|
ProcVolatility (..),
|
||||||
@@ -47,8 +47,9 @@ import PostgREST.DbStructure.Proc (ProcDescription (..),
|
|||||||
import PostgREST.DbStructure.Table (TablesMap)
|
import PostgREST.DbStructure.Table (TablesMap)
|
||||||
import PostgREST.Error (Error)
|
import PostgREST.Error (Error)
|
||||||
import PostgREST.MediaType (MediaType (..))
|
import PostgREST.MediaType (MediaType (..))
|
||||||
import PostgREST.Plan.CallPlan (CallPlan)
|
import PostgREST.Plan (CallReadPlan (..),
|
||||||
import PostgREST.Plan.MutatePlan (MutatePlan)
|
MutateReadPlan (..))
|
||||||
|
import PostgREST.Plan.MutatePlan (MutatePlan (..))
|
||||||
import PostgREST.Plan.ReadPlan (ReadPlanTree)
|
import PostgREST.Plan.ReadPlan (ReadPlanTree)
|
||||||
import PostgREST.Query.SqlFragment (fromQi, intercalateSnippet,
|
import PostgREST.Query.SqlFragment (fromQi, intercalateSnippet,
|
||||||
pgFmtIdentList,
|
pgFmtIdentList,
|
||||||
@@ -61,6 +62,7 @@ import PostgREST.Request.ApiRequest (Action (..),
|
|||||||
Target (..))
|
Target (..))
|
||||||
import PostgREST.Request.Preferences (PreferCount (..),
|
import PostgREST.Request.Preferences (PreferCount (..),
|
||||||
PreferParameters (..),
|
PreferParameters (..),
|
||||||
|
PreferTransaction (..),
|
||||||
shouldCount)
|
shouldCount)
|
||||||
|
|
||||||
import Protolude hiding (Handler)
|
import Protolude hiding (Handler)
|
||||||
@@ -85,6 +87,7 @@ readQuery req conf@AppConfig{..} apiReq@ApiRequest{..} = do
|
|||||||
iBinaryField
|
iBinaryField
|
||||||
configDbPreparedStatements
|
configDbPreparedStatements
|
||||||
failNotSingular iAcceptMediaType resultSet
|
failNotSingular iAcceptMediaType resultSet
|
||||||
|
optionalRollback conf apiReq
|
||||||
resultSetWTotal conf apiReq resultSet countQuery
|
resultSetWTotal conf apiReq resultSet countQuery
|
||||||
|
|
||||||
resultSetWTotal :: AppConfig -> ApiRequest -> ResultSet -> SQL.Snippet -> DbHandler ResultSet
|
resultSetWTotal :: AppConfig -> ApiRequest -> ResultSet -> SQL.Snippet -> DbHandler ResultSet
|
||||||
@@ -109,23 +112,26 @@ resultSetWTotal AppConfig{..} ApiRequest{..} rs@RSStandard{rsTableTotal=tableTot
|
|||||||
lift . SQL.statement mempty . Statements.preparePlanRows countQuery $
|
lift . SQL.statement mempty . Statements.preparePlanRows countQuery $
|
||||||
configDbPreparedStatements
|
configDbPreparedStatements
|
||||||
|
|
||||||
createQuery :: MutatePlan -> ReadPlanTree -> [FieldName] -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
createQuery :: MutateReadPlan -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
||||||
createQuery mutateReq readReq pkCols apiReq@ApiRequest{..} conf = do
|
createQuery mrPlan apiReq@ApiRequest{..} conf = do
|
||||||
resultSet <- writeQuery mutateReq readReq True pkCols apiReq conf
|
resultSet <- writeQuery mrPlan apiReq conf
|
||||||
failNotSingular iAcceptMediaType resultSet
|
failNotSingular iAcceptMediaType resultSet
|
||||||
|
optionalRollback conf apiReq
|
||||||
pure resultSet
|
pure resultSet
|
||||||
|
|
||||||
updateQuery :: MutatePlan -> ReadPlanTree -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
updateQuery :: MutateReadPlan -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
||||||
updateQuery mutateReq readReq apiReq@ApiRequest{..} conf = do
|
updateQuery mrPlan apiReq@ApiRequest{..} conf = do
|
||||||
resultSet <- writeQuery mutateReq readReq False mempty apiReq conf
|
resultSet <- writeQuery mrPlan apiReq conf
|
||||||
failNotSingular iAcceptMediaType resultSet
|
failNotSingular iAcceptMediaType resultSet
|
||||||
failsChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resultSet
|
failsChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resultSet
|
||||||
|
optionalRollback conf apiReq
|
||||||
pure resultSet
|
pure resultSet
|
||||||
|
|
||||||
singleUpsertQuery :: MutatePlan -> ReadPlanTree -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
singleUpsertQuery :: MutateReadPlan -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
||||||
singleUpsertQuery mutateReq readReq apiReq conf = do
|
singleUpsertQuery mrPlan apiReq conf = do
|
||||||
resultSet <- writeQuery mutateReq readReq False mempty apiReq conf
|
resultSet <- writeQuery mrPlan apiReq conf
|
||||||
failPut resultSet
|
failPut resultSet
|
||||||
|
optionalRollback conf apiReq
|
||||||
pure resultSet
|
pure resultSet
|
||||||
|
|
||||||
-- Makes sure the querystring pk matches the payload pk
|
-- Makes sure the querystring pk matches the payload pk
|
||||||
@@ -140,29 +146,31 @@ failPut RSStandard{rsQueryTotal=queryTotal} =
|
|||||||
lift SQL.condemn
|
lift SQL.condemn
|
||||||
throwError Error.PutMatchingPkError
|
throwError Error.PutMatchingPkError
|
||||||
|
|
||||||
deleteQuery :: MutatePlan -> ReadPlanTree -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
deleteQuery :: MutateReadPlan -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
||||||
deleteQuery mutateReq readReq apiReq@ApiRequest{..} conf = do
|
deleteQuery mrPlan apiReq@ApiRequest{..} conf = do
|
||||||
resultSet <- writeQuery mutateReq readReq False mempty apiReq conf
|
resultSet <- writeQuery mrPlan apiReq conf
|
||||||
failNotSingular iAcceptMediaType resultSet
|
failNotSingular iAcceptMediaType resultSet
|
||||||
failsChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resultSet
|
failsChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resultSet
|
||||||
|
optionalRollback conf apiReq
|
||||||
pure resultSet
|
pure resultSet
|
||||||
|
|
||||||
invokeQuery :: ProcDescription -> CallPlan -> ReadPlanTree -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
invokeQuery :: ProcDescription -> CallReadPlan -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
||||||
invokeQuery proc callReq readReq ApiRequest{..} AppConfig{..} = do
|
invokeQuery proc CallReadPlan{crReadPlan, crCallPlan} apiReq@ApiRequest{..} conf@AppConfig{..} = do
|
||||||
resultSet <-
|
resultSet <-
|
||||||
lift . SQL.statement mempty $
|
lift . SQL.statement mempty $
|
||||||
Statements.prepareCall
|
Statements.prepareCall
|
||||||
(Proc.procReturnsScalar proc)
|
(Proc.procReturnsScalar proc)
|
||||||
(Proc.procReturnsSingle proc)
|
(Proc.procReturnsSingle proc)
|
||||||
(QueryBuilder.callPlanToQuery callReq)
|
(QueryBuilder.callPlanToQuery crCallPlan)
|
||||||
(QueryBuilder.readPlanToQuery readReq)
|
(QueryBuilder.readPlanToQuery crReadPlan)
|
||||||
(QueryBuilder.readPlanToCountQuery readReq)
|
(QueryBuilder.readPlanToCountQuery crReadPlan)
|
||||||
(shouldCount iPreferCount)
|
(shouldCount iPreferCount)
|
||||||
iAcceptMediaType
|
iAcceptMediaType
|
||||||
(iPreferParameters == Just MultipleObjects)
|
(iPreferParameters == Just MultipleObjects)
|
||||||
iBinaryField
|
iBinaryField
|
||||||
configDbPreparedStatements
|
configDbPreparedStatements
|
||||||
|
|
||||||
|
optionalRollback conf apiReq
|
||||||
failNotSingular iAcceptMediaType resultSet
|
failNotSingular iAcceptMediaType resultSet
|
||||||
pure resultSet
|
pure resultSet
|
||||||
|
|
||||||
@@ -200,12 +208,15 @@ txMode ApiRequest{..} =
|
|||||||
_ ->
|
_ ->
|
||||||
SQL.Write
|
SQL.Write
|
||||||
|
|
||||||
writeQuery :: MutatePlan -> ReadPlanTree -> Bool -> [Text] -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
writeQuery :: MutateReadPlan -> ApiRequest -> AppConfig -> DbHandler ResultSet
|
||||||
writeQuery mutateReq readReq isInsert pkCols apiReq conf = do
|
writeQuery MutateReadPlan{mrReadPlan, mrMutatePlan} apiReq conf =
|
||||||
|
let
|
||||||
|
(isInsert, pkCols) = case mrMutatePlan of {Insert{insPkCols} -> (True, insPkCols); _ -> (False, mempty);}
|
||||||
|
in
|
||||||
lift . SQL.statement mempty $
|
lift . SQL.statement mempty $
|
||||||
Statements.prepareWrite
|
Statements.prepareWrite
|
||||||
(QueryBuilder.readPlanToQuery readReq)
|
(QueryBuilder.readPlanToQuery mrReadPlan)
|
||||||
(QueryBuilder.mutatePlanToQuery mutateReq)
|
(QueryBuilder.mutatePlanToQuery mrMutatePlan)
|
||||||
isInsert
|
isInsert
|
||||||
(iAcceptMediaType apiReq)
|
(iAcceptMediaType apiReq)
|
||||||
(iPreferRepresentation apiReq)
|
(iPreferRepresentation apiReq)
|
||||||
@@ -230,6 +241,18 @@ failsChangesOffLimits (Just maxChanges) RSStandard{rsQueryTotal=queryTotal} =
|
|||||||
lift SQL.condemn
|
lift SQL.condemn
|
||||||
throwError $ Error.OffLimitsChangesError queryTotal maxChanges
|
throwError $ Error.OffLimitsChangesError queryTotal maxChanges
|
||||||
|
|
||||||
|
-- | Set a transaction to roll back if requested
|
||||||
|
optionalRollback :: AppConfig -> ApiRequest -> DbHandler ()
|
||||||
|
optionalRollback AppConfig{..} ApiRequest{..} = do
|
||||||
|
lift $ when (shouldRollback || (configDbTxRollbackAll && not shouldCommit)) $ do
|
||||||
|
SQL.sql "SET CONSTRAINTS ALL IMMEDIATE"
|
||||||
|
SQL.condemn
|
||||||
|
where
|
||||||
|
shouldCommit =
|
||||||
|
configDbTxAllowOverride && iPreferTransaction == Just Commit
|
||||||
|
shouldRollback =
|
||||||
|
configDbTxAllowOverride && iPreferTransaction == Just Rollback
|
||||||
|
|
||||||
-- | Runs local(transaction scoped) GUCs for every request, plus the pre-request function
|
-- | Runs local(transaction scoped) GUCs for every request, plus the pre-request function
|
||||||
setPgLocals :: AppConfig -> KM.KeyMap JSON.Value -> Text ->
|
setPgLocals :: AppConfig -> KM.KeyMap JSON.Value -> Text ->
|
||||||
ApiRequest -> ByteString -> PgVersion -> DbHandler ()
|
ApiRequest -> ByteString -> PgVersion -> DbHandler ()
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ getSelectsJoins rr@(Node ReadPlan{nodeName=name, nodeRel=Just rel, nodeAlias=ali
|
|||||||
(sel:selects, joi:joins)
|
(sel:selects, joi:joins)
|
||||||
|
|
||||||
mutatePlanToQuery :: MutatePlan -> SQL.Snippet
|
mutatePlanToQuery :: MutatePlan -> SQL.Snippet
|
||||||
mutatePlanToQuery (Insert mainQi iCols body onConflct putConditions returnings) =
|
mutatePlanToQuery (Insert mainQi iCols body onConflct putConditions returnings _) =
|
||||||
"WITH " <> normalizedBody body <> " " <>
|
"WITH " <> normalizedBody body <> " " <>
|
||||||
"INSERT INTO " <> SQL.sql (fromQi mainQi) <> SQL.sql (if S.null iCols then " " else "(" <> cols <> ") ") <>
|
"INSERT INTO " <> SQL.sql (fromQi mainQi) <> SQL.sql (if S.null iCols then " " else "(" <> cols <> ") ") <>
|
||||||
"SELECT " <> SQL.sql cols <> " " <>
|
"SELECT " <> SQL.sql cols <> " " <>
|
||||||
|
|||||||
+37
-10
@@ -1,3 +1,4 @@
|
|||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
module PostgREST.Response
|
module PostgREST.Response
|
||||||
( createResponse
|
( createResponse
|
||||||
@@ -10,6 +11,7 @@ module PostgREST.Response
|
|||||||
, updateResponse
|
, updateResponse
|
||||||
, addRetryHint
|
, addRetryHint
|
||||||
, isServiceUnavailable
|
, isServiceUnavailable
|
||||||
|
, optionalRollback
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
@@ -30,8 +32,7 @@ import qualified PostgREST.Response.OpenAPI as OpenAPI
|
|||||||
|
|
||||||
import PostgREST.Config (AppConfig (..))
|
import PostgREST.Config (AppConfig (..))
|
||||||
import PostgREST.DbStructure (DbStructure (..))
|
import PostgREST.DbStructure (DbStructure (..))
|
||||||
import PostgREST.DbStructure.Identifiers (FieldName,
|
import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..),
|
||||||
QualifiedIdentifier (..),
|
|
||||||
Schema)
|
Schema)
|
||||||
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
||||||
ProcVolatility (..),
|
ProcVolatility (..),
|
||||||
@@ -41,11 +42,14 @@ import PostgREST.GucHeader (GucHeader,
|
|||||||
addHeadersIfNotIncluded,
|
addHeadersIfNotIncluded,
|
||||||
unwrapGucHeader)
|
unwrapGucHeader)
|
||||||
import PostgREST.MediaType (MediaType (..))
|
import PostgREST.MediaType (MediaType (..))
|
||||||
|
import PostgREST.Plan (MutateReadPlan (..))
|
||||||
|
import PostgREST.Plan.MutatePlan (MutatePlan (..))
|
||||||
import PostgREST.Query.Statements (ResultSet (..))
|
import PostgREST.Query.Statements (ResultSet (..))
|
||||||
import PostgREST.Request.ApiRequest (ApiRequest (..),
|
import PostgREST.Request.ApiRequest (ApiRequest (..),
|
||||||
InvokeMethod (..),
|
InvokeMethod (..),
|
||||||
Target (..))
|
Target (..))
|
||||||
import PostgREST.Request.Preferences (PreferRepresentation (..),
|
import PostgREST.Request.Preferences (PreferRepresentation (..),
|
||||||
|
PreferTransaction (..),
|
||||||
shouldCount,
|
shouldCount,
|
||||||
toAppliedHeader)
|
toAppliedHeader)
|
||||||
import PostgREST.Request.QueryParams (QueryParams (..))
|
import PostgREST.Request.QueryParams (QueryParams (..))
|
||||||
@@ -81,10 +85,11 @@ readResponse headersOnly identifier ctxApiRequest@ApiRequest{..} resultSet = cas
|
|||||||
RSPlan plan ->
|
RSPlan plan ->
|
||||||
Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan
|
Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan
|
||||||
|
|
||||||
createResponse :: QualifiedIdentifier -> [FieldName] -> ApiRequest -> ResultSet -> Wai.Response
|
createResponse :: QualifiedIdentifier -> MutateReadPlan -> ApiRequest -> ResultSet -> Wai.Response
|
||||||
createResponse QualifiedIdentifier{..} pkCols ctxApiRequest@ApiRequest{..} resultSet = case resultSet of
|
createResponse QualifiedIdentifier{..} MutateReadPlan{mrMutatePlan} ctxApiRequest@ApiRequest{..} resultSet = case resultSet of
|
||||||
RSStandard{..} -> do
|
RSStandard{..} -> do
|
||||||
let
|
let
|
||||||
|
pkCols = case mrMutatePlan of { Insert{insPkCols} -> insPkCols; _ -> mempty;}
|
||||||
response = gucResponse rsGucStatus rsGucHeaders
|
response = gucResponse rsGucStatus rsGucHeaders
|
||||||
headers =
|
headers =
|
||||||
catMaybes
|
catMaybes
|
||||||
@@ -127,11 +132,12 @@ updateResponse ctxApiRequest@ApiRequest{..} resultSet = case resultSet of
|
|||||||
contentRangeHeader =
|
contentRangeHeader =
|
||||||
RangeQuery.contentRangeH 0 (rsQueryTotal - 1) $
|
RangeQuery.contentRangeH 0 (rsQueryTotal - 1) $
|
||||||
if shouldCount iPreferCount then Just rsQueryTotal else Nothing
|
if shouldCount iPreferCount then Just rsQueryTotal else Nothing
|
||||||
|
headers = [contentRangeHeader]
|
||||||
|
|
||||||
if fullRepr then
|
if fullRepr then
|
||||||
response status (contentTypeHeaders ctxApiRequest ++ [contentRangeHeader]) (LBS.fromStrict rsBody)
|
response status (headers ++ contentTypeHeaders ctxApiRequest) (LBS.fromStrict rsBody)
|
||||||
else
|
else
|
||||||
response status [contentRangeHeader] mempty
|
response status headers mempty
|
||||||
|
|
||||||
RSPlan plan ->
|
RSPlan plan ->
|
||||||
Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan
|
Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan
|
||||||
@@ -158,13 +164,14 @@ deleteResponse ctxApiRequest@ApiRequest{..} resultSet = case resultSet of
|
|||||||
contentRangeHeader =
|
contentRangeHeader =
|
||||||
RangeQuery.contentRangeH 1 0 $
|
RangeQuery.contentRangeH 1 0 $
|
||||||
if shouldCount iPreferCount then Just rsQueryTotal else Nothing
|
if shouldCount iPreferCount then Just rsQueryTotal else Nothing
|
||||||
|
headers = [contentRangeHeader]
|
||||||
|
|
||||||
if iPreferRepresentation == Full then
|
if iPreferRepresentation == Full then
|
||||||
response HTTP.status200
|
response HTTP.status200
|
||||||
(contentTypeHeaders ctxApiRequest ++ [contentRangeHeader])
|
(headers ++ contentTypeHeaders ctxApiRequest)
|
||||||
(LBS.fromStrict rsBody)
|
(LBS.fromStrict rsBody)
|
||||||
else
|
else
|
||||||
response HTTP.status204 [contentRangeHeader] mempty
|
response HTTP.status204 headers mempty
|
||||||
|
|
||||||
RSPlan plan ->
|
RSPlan plan ->
|
||||||
Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan
|
Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan
|
||||||
@@ -203,12 +210,13 @@ invokeResponse invMethod proc ctxApiRequest@ApiRequest{..} resultSet = case resu
|
|||||||
then Error.errorPayload $ Error.ApiRequestError $ ApiRequestTypes.InvalidRange
|
then Error.errorPayload $ Error.ApiRequestError $ ApiRequestTypes.InvalidRange
|
||||||
$ ApiRequestTypes.OutOfBounds (show $ RangeQuery.rangeOffset iTopLevelRange) (maybe "0" show rsTableTotal)
|
$ ApiRequestTypes.OutOfBounds (show $ RangeQuery.rangeOffset iTopLevelRange) (maybe "0" show rsTableTotal)
|
||||||
else LBS.fromStrict rsBody
|
else LBS.fromStrict rsBody
|
||||||
|
headers = [contentRange]
|
||||||
|
|
||||||
if Proc.procReturnsVoid proc then
|
if Proc.procReturnsVoid proc then
|
||||||
response HTTP.status204 [contentRange] mempty
|
response HTTP.status204 headers mempty
|
||||||
else
|
else
|
||||||
response status
|
response status
|
||||||
(contentTypeHeaders ctxApiRequest ++ [contentRange])
|
(headers ++ contentTypeHeaders ctxApiRequest)
|
||||||
(if invMethod == InvHead then mempty else rsOrErrBody)
|
(if invMethod == InvHead then mempty else rsOrErrBody)
|
||||||
|
|
||||||
RSPlan plan ->
|
RSPlan plan ->
|
||||||
@@ -260,3 +268,22 @@ addRetryHint delay response = do
|
|||||||
|
|
||||||
isServiceUnavailable :: Wai.Response -> Bool
|
isServiceUnavailable :: Wai.Response -> Bool
|
||||||
isServiceUnavailable response = Wai.responseStatus response == HTTP.status503
|
isServiceUnavailable response = Wai.responseStatus response == HTTP.status503
|
||||||
|
|
||||||
|
optionalRollback :: AppConfig -> ApiRequest -> ExceptT Error.Error IO Wai.Response -> ExceptT Error.Error IO Wai.Response
|
||||||
|
optionalRollback AppConfig{..} ApiRequest{..} resp = do
|
||||||
|
newRes <- catchError resp $ return . Error.errorResponseFor
|
||||||
|
return $ Wai.mapResponseHeaders preferenceApplied newRes
|
||||||
|
where
|
||||||
|
shouldCommit =
|
||||||
|
configDbTxAllowOverride && iPreferTransaction == Just Commit
|
||||||
|
shouldRollback =
|
||||||
|
configDbTxAllowOverride && iPreferTransaction == Just Rollback
|
||||||
|
preferenceApplied
|
||||||
|
| shouldCommit =
|
||||||
|
addHeadersIfNotIncluded
|
||||||
|
[toAppliedHeader Commit]
|
||||||
|
| shouldRollback =
|
||||||
|
addHeadersIfNotIncluded
|
||||||
|
[toAppliedHeader Rollback]
|
||||||
|
| otherwise =
|
||||||
|
identity
|
||||||
|
|||||||
@@ -808,6 +808,16 @@ def test_no_pool_connection_required_on_bad_jwt_claim(defaultenv):
|
|||||||
assert response.status_code == 401
|
assert response.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_pool_connection_required_on_bad_embedding(defaultenv):
|
||||||
|
"no pool connection should be consumed for failing to embed"
|
||||||
|
|
||||||
|
with run(env=defaultenv, no_pool_connection_available=True) as postgrest:
|
||||||
|
|
||||||
|
# OPTIONS on a table shouldn't require opening a connection
|
||||||
|
response = postgrest.session.get("/projects?select=*,unexistent(*)")
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
# TODO: This test fails now because of https://github.com/PostgREST/postgrest/pull/2122
|
# TODO: This test fails now because of https://github.com/PostgREST/postgrest/pull/2122
|
||||||
# The stack size of 1K(-with-rtsopts=-K1K) is not enough and this fails with "stack overflow"
|
# The stack size of 1K(-with-rtsopts=-K1K) is not enough and this fails with "stack overflow"
|
||||||
# A stack size of 200K seems to be enough for succeess
|
# A stack size of 200K seems to be enough for succeess
|
||||||
|
|||||||
Reference in New Issue
Block a user