feat: Add compatibility with connection poolers on transaction mode
Update the hasql-transaction library to version 1.0.1 Add hints and kill thread at configuration read when using incompatible pooling modes: statement pooling and transaction pooling with prepared statements enabled.
This commit is contained in:
+3
-3
@@ -87,7 +87,7 @@ library
|
|||||||
, hasql-dynamic-statements == 0.3.1
|
, hasql-dynamic-statements == 0.3.1
|
||||||
, hasql-notifications >= 0.1 && < 0.3
|
, hasql-notifications >= 0.1 && < 0.3
|
||||||
, hasql-pool >= 0.5 && < 0.6
|
, hasql-pool >= 0.5 && < 0.6
|
||||||
, hasql-transaction >= 0.7.2 && < 1.1
|
, hasql-transaction >= 1.0.1 && < 1.1
|
||||||
, heredoc >= 0.2 && < 0.3
|
, heredoc >= 0.2 && < 0.3
|
||||||
, http-types >= 0.12.2 && < 0.13
|
, http-types >= 0.12.2 && < 0.13
|
||||||
, insert-ordered-containers >= 0.2.2 && < 0.3
|
, insert-ordered-containers >= 0.2.2 && < 0.3
|
||||||
@@ -214,7 +214,7 @@ test-suite spec
|
|||||||
, contravariant >= 1.4 && < 1.6
|
, contravariant >= 1.4 && < 1.6
|
||||||
, hasql >= 1.4 && < 1.5
|
, hasql >= 1.4 && < 1.5
|
||||||
, hasql-pool >= 0.5 && < 0.6
|
, hasql-pool >= 0.5 && < 0.6
|
||||||
, hasql-transaction >= 0.7.2 && < 1.1
|
, hasql-transaction >= 1.0.1 && < 1.1
|
||||||
, heredoc >= 0.2 && < 0.3
|
, heredoc >= 0.2 && < 0.3
|
||||||
, hspec >= 2.3 && < 2.8
|
, hspec >= 2.3 && < 2.8
|
||||||
, hspec-wai >= 0.10 && < 0.12
|
, hspec-wai >= 0.10 && < 0.12
|
||||||
@@ -259,7 +259,7 @@ test-suite spec-querycost
|
|||||||
, hasql >= 1.4 && < 1.5
|
, hasql >= 1.4 && < 1.5
|
||||||
, hasql-dynamic-statements == 0.3.1
|
, hasql-dynamic-statements == 0.3.1
|
||||||
, hasql-pool >= 0.5 && < 0.6
|
, hasql-pool >= 0.5 && < 0.6
|
||||||
, hasql-transaction >= 0.7.2 && < 1.1
|
, hasql-transaction >= 1.0.1 && < 1.1
|
||||||
, heredoc >= 0.2 && < 0.3
|
, heredoc >= 0.2 && < 0.3
|
||||||
, hspec >= 2.3 && < 2.8
|
, hspec >= 2.3 && < 2.8
|
||||||
, hspec-wai >= 0.10 && < 0.12
|
, hspec-wai >= 0.10 && < 0.12
|
||||||
|
|||||||
@@ -190,14 +190,15 @@ postgrestResponse conf maybeDbStructure jsonDbS pgVer pool time req = do
|
|||||||
handleReq apiReq =
|
handleReq apiReq =
|
||||||
handleRequest $ RequestContext conf dbStructure apiReq pgVer
|
handleRequest $ RequestContext conf dbStructure apiReq pgVer
|
||||||
|
|
||||||
runDbHandler pool (txMode apiRequest) jwtClaims .
|
runDbHandler pool (txMode apiRequest) jwtClaims (configDbPreparedStatements conf) .
|
||||||
Middleware.optionalRollback conf apiRequest $
|
Middleware.optionalRollback conf apiRequest $
|
||||||
Middleware.runPgLocals conf jwtClaims handleReq apiRequest jsonDbS
|
Middleware.runPgLocals conf jwtClaims handleReq apiRequest jsonDbS
|
||||||
|
|
||||||
runDbHandler :: SQL.Pool -> SQL.Mode -> Auth.JWTClaims -> DbHandler a -> Handler IO a
|
runDbHandler :: SQL.Pool -> SQL.Mode -> Auth.JWTClaims -> Bool -> DbHandler a -> Handler IO a
|
||||||
runDbHandler pool mode jwtClaims handler = do
|
runDbHandler pool mode jwtClaims prepared handler = do
|
||||||
dbResp <-
|
dbResp <-
|
||||||
lift . SQL.use pool . SQL.transaction SQL.ReadCommitted mode $ runExceptT handler
|
let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction in
|
||||||
|
lift . SQL.use pool . transaction SQL.ReadCommitted mode $ runExceptT handler
|
||||||
|
|
||||||
resp <-
|
resp <-
|
||||||
liftEither . mapLeft Error.PgErr $
|
liftEither . mapLeft Error.PgErr $
|
||||||
|
|||||||
@@ -54,8 +54,9 @@ dumpSchema :: AppState -> IO LBS.ByteString
|
|||||||
dumpSchema appState = do
|
dumpSchema appState = do
|
||||||
AppConfig{..} <- AppState.getConfig appState
|
AppConfig{..} <- AppState.getConfig appState
|
||||||
result <-
|
result <-
|
||||||
|
let transaction = if configDbPreparedStatements then HT.transaction else HT.unpreparedTransaction in
|
||||||
P.use (AppState.getPool appState) $
|
P.use (AppState.getPool appState) $
|
||||||
HT.transaction HT.ReadCommitted HT.Read $
|
transaction HT.ReadCommitted HT.Read $
|
||||||
queryDbStructure
|
queryDbStructure
|
||||||
(toList configDbSchemas)
|
(toList configDbSchemas)
|
||||||
configDbExtraSearchPath
|
configDbExtraSearchPath
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ queryPgVersion = H.statement mempty $ H.Statement sql HE.noParams versionRow Fal
|
|||||||
sql = "SELECT current_setting('server_version_num')::integer, current_setting('server_version')"
|
sql = "SELECT current_setting('server_version_num')::integer, current_setting('server_version')"
|
||||||
versionRow = HD.singleRow $ PgVersion <$> column HD.int4 <*> column HD.text
|
versionRow = HD.singleRow $ PgVersion <$> column HD.int4 <*> column HD.text
|
||||||
|
|
||||||
queryDbSettings :: P.Pool -> IO (Either P.UsageError [(Text, Text)])
|
queryDbSettings :: P.Pool -> Bool -> IO (Either P.UsageError [(Text, Text)])
|
||||||
queryDbSettings pool =
|
queryDbSettings pool prepared =
|
||||||
P.use pool . HT.transaction HT.ReadCommitted HT.Read $
|
let transaction = if prepared then HT.transaction else HT.unpreparedTransaction in
|
||||||
|
P.use pool . transaction HT.ReadCommitted HT.Read $
|
||||||
HT.statement mempty dbSettingsStatement
|
HT.statement mempty dbSettingsStatement
|
||||||
|
|
||||||
-- | Get db settings from the connection role. Global settings will be overridden by database specific settings.
|
-- | Get db settings from the connection role. Global settings will be overridden by database specific settings.
|
||||||
|
|||||||
+16
-2
@@ -239,8 +239,22 @@ checkIsFatal (PgError _ (P.ConnectionError e))
|
|||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
where isAuthFailureMessage = "FATAL: password authentication failed" `isPrefixOf` toS failureMessage
|
where isAuthFailureMessage = "FATAL: password authentication failed" `isPrefixOf` toS failureMessage
|
||||||
failureMessage = fromMaybe mempty e
|
failureMessage = fromMaybe mempty e
|
||||||
-- Chek for a syntax error(42601 is the pg code). This would mean the error is on our part somehow, so we treat it as fatal.
|
checkIsFatal (PgError _ (P.SessionError (H.QueryError _ _ (H.ResultError serverError))))
|
||||||
checkIsFatal (PgError _ (P.SessionError (H.QueryError _ _ (H.ResultError (H.ServerError "42601" e _ _))))) = Just $ toS e
|
= case serverError of
|
||||||
|
-- Check for a syntax error (42601 is the pg code). This would mean the error is on our part somehow, so we treat it as fatal.
|
||||||
|
H.ServerError "42601" _ _ _
|
||||||
|
-> Just "Hint: This is probably a bug in PostgREST, please report it at https://github.com/PostgREST/postgrest/issues"
|
||||||
|
-- Check for a "prepared statement <name> already exists" error (Code 42P05: duplicate_prepared_statement).
|
||||||
|
-- This would mean that a connection pooler in transaction mode is being used
|
||||||
|
-- while prepared statements are enabled in the PostgREST configuration,
|
||||||
|
-- both of which are incompatible with each other.
|
||||||
|
H.ServerError "42P05" _ _ _
|
||||||
|
-> Just "Hint: If you are using connection poolers in transaction mode, try setting db-prepared-statements to false."
|
||||||
|
-- Check for a "transaction blocks not allowed in statement pooling mode" error (Code 08P01: protocol_violation).
|
||||||
|
-- This would mean that a connection pooler in statement mode is being used which is not supported in PostgREST.
|
||||||
|
H.ServerError "08P01" "transaction blocks not allowed in statement pooling mode" _ _
|
||||||
|
-> Just "Hint: Connection poolers in statement mode are not supported."
|
||||||
|
_ -> Nothing
|
||||||
checkIsFatal _ = Nothing
|
checkIsFatal _ = Nothing
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,8 @@ loadSchemaCache :: AppState -> IO SCacheStatus
|
|||||||
loadSchemaCache appState = do
|
loadSchemaCache appState = do
|
||||||
AppConfig{..} <- AppState.getConfig appState
|
AppConfig{..} <- AppState.getConfig appState
|
||||||
result <-
|
result <-
|
||||||
P.use (AppState.getPool appState) . HT.transaction HT.ReadCommitted HT.Read $
|
let transaction = if configDbPreparedStatements then HT.transaction else HT.unpreparedTransaction in
|
||||||
|
P.use (AppState.getPool appState) . transaction HT.ReadCommitted HT.Read $
|
||||||
queryDbStructure (toList configDbSchemas) configDbExtraSearchPath configDbPreparedStatements
|
queryDbStructure (toList configDbSchemas) configDbExtraSearchPath configDbPreparedStatements
|
||||||
case result of
|
case result of
|
||||||
Left e -> do
|
Left e -> do
|
||||||
@@ -159,12 +160,10 @@ loadSchemaCache appState = do
|
|||||||
err = PgError False e
|
err = PgError False e
|
||||||
putErr = AppState.logWithZTime appState . toS $ errorPayload err
|
putErr = AppState.logWithZTime appState . toS $ errorPayload err
|
||||||
case checkIsFatal err of
|
case checkIsFatal err of
|
||||||
Just _ -> do
|
Just hint -> do
|
||||||
AppState.logWithZTime appState "A fatal error ocurred when loading the schema cache"
|
AppState.logWithZTime appState "A fatal error ocurred when loading the schema cache"
|
||||||
putErr
|
putErr
|
||||||
AppState.logWithZTime appState $
|
AppState.logWithZTime appState hint
|
||||||
"This is probably a bug in PostgREST, please report it at "
|
|
||||||
<> "https://github.com/PostgREST/postgrest/issues"
|
|
||||||
return SCFatalFail
|
return SCFatalFail
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
AppState.logWithZTime appState "An error ocurred when loading the schema cache"
|
AppState.logWithZTime appState "An error ocurred when loading the schema cache"
|
||||||
@@ -229,12 +228,21 @@ reReadConfig startingUp appState = do
|
|||||||
AppConfig{..} <- AppState.getConfig appState
|
AppConfig{..} <- AppState.getConfig appState
|
||||||
dbSettings <-
|
dbSettings <-
|
||||||
if configDbConfig then do
|
if configDbConfig then do
|
||||||
qDbSettings <- queryDbSettings $ AppState.getPool appState
|
qDbSettings <- queryDbSettings (AppState.getPool appState) configDbPreparedStatements
|
||||||
case qDbSettings of
|
case qDbSettings of
|
||||||
Left e -> do
|
Left e -> do
|
||||||
AppState.logWithZTime appState $
|
let
|
||||||
"An error ocurred when trying to query database settings for the config parameters:\n"
|
err = PgError False e
|
||||||
<> show e
|
putErr = AppState.logWithZTime appState . toS $ errorPayload err
|
||||||
|
AppState.logWithZTime appState
|
||||||
|
"An error ocurred when trying to query database settings for the config parameters"
|
||||||
|
case checkIsFatal err of
|
||||||
|
Just hint -> do
|
||||||
|
putErr
|
||||||
|
AppState.logWithZTime appState hint
|
||||||
|
killThread (AppState.getMainThreadId appState)
|
||||||
|
Nothing -> do
|
||||||
|
AppState.logWithZTime appState $ show e
|
||||||
pure []
|
pure []
|
||||||
Right x -> pure x
|
Right x -> pure x
|
||||||
else
|
else
|
||||||
|
|||||||
Vendored
+4
-3
@@ -2041,9 +2041,10 @@ returns setof v2.parents as $$
|
|||||||
select * from v2.parents where id < $1;
|
select * from v2.parents where id < $1;
|
||||||
$$ language sql;
|
$$ language sql;
|
||||||
|
|
||||||
-- Only used for manually testing creating prepared statements
|
-- Used to test if prepared statements are used
|
||||||
create view prepared_statements as
|
create function uses_prepared_statements() returns bool as $$
|
||||||
select * from pg_catalog.pg_prepared_statements;
|
select count(name) > 0 from pg_catalog.pg_prepared_statements
|
||||||
|
$$ language sql;
|
||||||
|
|
||||||
create or replace function change_max_rows_config(val int, notify bool default false) returns void as $_$
|
create or replace function change_max_rows_config(val int, notify bool default false) returns void as $_$
|
||||||
begin
|
begin
|
||||||
|
|||||||
@@ -708,3 +708,22 @@ def test_invalid_role_claim_key_notify_reload(defaultenv):
|
|||||||
)
|
)
|
||||||
|
|
||||||
postgrest.session.post("/rpc/reset_invalid_role_claim_key")
|
postgrest.session.post("/rpc/reset_invalid_role_claim_key")
|
||||||
|
|
||||||
|
def test_db_prepared_statements_enable(defaultenv):
|
||||||
|
"Should use prepared statements when the setting is enabled."
|
||||||
|
|
||||||
|
with run(env=defaultenv) as postgrest:
|
||||||
|
response = postgrest.session.post("/rpc/uses_prepared_statements")
|
||||||
|
assert response.text == "true"
|
||||||
|
|
||||||
|
def test_db_prepared_statements_disable(defaultenv):
|
||||||
|
"Should not use any prepared statements when the setting is disabled."
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_DB_PREPARED_STATEMENTS": "false",
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env) as postgrest:
|
||||||
|
response = postgrest.session.post("/rpc/uses_prepared_statements")
|
||||||
|
assert response.text == "false"
|
||||||
|
|||||||
Reference in New Issue
Block a user