fix: not logging OpenAPI queries
Closes https://github.com/PostgREST/postgrest/issues/4226. This requires moving query generation to the top App.hs module. At this point is also simple to log the transaction variables + the pre-request function call but this is not done here to reduce scope.
This commit is contained in:
committed by
Steve Chavez
parent
1d2a3e8501
commit
cddfb6cf5e
@@ -5,6 +5,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix not logging OpenAPI queries when `log-query=main-query` is enabled by @steve-chavez in #4226
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Improve the `PGRST106` error when the requested schema is invalid by @laurenceisla in #4089
|
- Improve the `PGRST106` error when the requested schema is invalid by @laurenceisla in #4089
|
||||||
|
|||||||
+15
-6
@@ -142,21 +142,30 @@ postgrestResponse appState conf@AppConfig{..} maybeSchemaCache authResult@AuthRe
|
|||||||
(parseTime, apiReq@ApiRequest{..}) <- withTiming $ liftEither . mapLeft Error.ApiRequestError $ ApiRequest.userApiRequest conf prefs req body
|
(parseTime, apiReq@ApiRequest{..}) <- withTiming $ liftEither . mapLeft Error.ApiRequestError $ ApiRequest.userApiRequest conf prefs req body
|
||||||
(planTime, plan) <- withTiming $ liftEither $ Plan.actionPlan iAction conf apiReq sCache
|
(planTime, plan) <- withTiming $ liftEither $ Plan.actionPlan iAction conf apiReq sCache
|
||||||
|
|
||||||
let query = Query.query conf authResult apiReq plan sCache
|
let mainQ = Query.mainQuery plan conf apiReq authResult configDbPreRequest
|
||||||
logSQL = lift . AppState.getObserver appState . DBQuery (Query.getSQLQuery query)
|
query = Query.mainTx mainQ conf authResult apiReq plan sCache
|
||||||
|
observer = AppState.getObserver appState
|
||||||
|
obsQuery s = when (configLogQuery /= LogQueryDisabled) $ observer $ QueryObs mainQ s
|
||||||
|
|
||||||
(queryTime, queryResult) <- withTiming $ do
|
(queryTime, queryResult) <- withTiming $ do
|
||||||
case query of
|
case query of
|
||||||
Query.NoDbQuery r -> pure r
|
Query.NoDbQuery r -> pure r
|
||||||
Query.DbQuery{..} -> do
|
Query.DbQuery{..} -> do
|
||||||
dbRes <- lift $ AppState.usePool appState (dqTransaction dqIsoLevel dqTxMode $ runExceptT dqDbHandler)
|
dbRes <- lift $ AppState.usePool appState (dqTransaction dqIsoLevel dqTxMode $ runExceptT dqDbHandler)
|
||||||
let eitherResp = mapLeft Error.PgErr . mapLeft (Error.PgError (Just authRole /= configDbAnonRole)) $ dbRes
|
let eitherResp = join $ mapLeft (Error.PgErr . Error.PgError (Just authRole /= configDbAnonRole)) dbRes
|
||||||
when (configLogQuery /= LogQueryDisabled) $ whenLeft eitherResp $ logSQL . Error.status
|
|
||||||
liftEither eitherResp >>= liftEither
|
-- TODO: we use obsQuery twice, one here and one below because in case of an error with the usePool above, the request will finish here and return an error message.
|
||||||
|
-- This is because of a combination of ExceptT + our Error module which has Wai.responseLBS.
|
||||||
|
-- This needs refactoring so only the below obsQuery is used.
|
||||||
|
lift $ whenLeft eitherResp $ obsQuery . Error.status
|
||||||
|
liftEither eitherResp
|
||||||
|
|
||||||
(respTime, resp) <- withTiming $ do
|
(respTime, resp) <- withTiming $ do
|
||||||
let response = Response.actionResponse queryResult apiReq (T.decodeUtf8 prettyVersion, docsVersion) conf sCache iSchema iNegotiatedByProfile
|
let response = Response.actionResponse queryResult apiReq (T.decodeUtf8 prettyVersion, docsVersion) conf sCache iSchema iNegotiatedByProfile
|
||||||
when (configLogQuery /= LogQueryDisabled) $ logSQL $ either Error.status Response.pgrstStatus response
|
status' = either Error.status Response.pgrstStatus response
|
||||||
|
|
||||||
|
-- TODO: see above obsQuery, only this obsQuery should remain after refactoring (because the QueryObs depends on the status)
|
||||||
|
lift $ obsQuery status'
|
||||||
liftEither response
|
liftEither response
|
||||||
|
|
||||||
return $ toWaiResponse (ServerTiming jwtTime parseTime planTime queryTime respTime) resp
|
return $ toWaiResponse (ServerTiming jwtTime parseTime planTime queryTime respTime) resp
|
||||||
|
|||||||
+29
-5
@@ -1,3 +1,4 @@
|
|||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
{-|
|
{-|
|
||||||
Module : PostgREST.Logger
|
Module : PostgREST.Logger
|
||||||
Description : Logging based on the Observation.hs module. Access logs get sent to stdout and server diagnostic get sent to stderr.
|
Description : Logging based on the Observation.hs module. Access logs get sent to stdout and server diagnostic get sent to stderr.
|
||||||
@@ -11,9 +12,15 @@ module PostgREST.Logger
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.AutoUpdate (defaultUpdateSettings,
|
import Control.AutoUpdate (defaultUpdateSettings,
|
||||||
mkAutoUpdate, updateAction)
|
mkAutoUpdate,
|
||||||
|
updateAction)
|
||||||
import Control.Debounce
|
import Control.Debounce
|
||||||
import qualified Data.ByteString.Char8 as BS
|
import qualified Data.ByteString.Char8 as BS
|
||||||
|
import qualified Data.Text.Encoding as T
|
||||||
|
import qualified Hasql.Decoders as HD
|
||||||
|
import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql)
|
||||||
|
import qualified Hasql.DynamicStatements.Statement as SQL
|
||||||
|
import qualified Hasql.Statement as SQL
|
||||||
|
|
||||||
import Data.Time (ZonedTime, defaultTimeLocale, formatTime,
|
import Data.Time (ZonedTime, defaultTimeLocale, formatTime,
|
||||||
getZonedTime)
|
getZonedTime)
|
||||||
@@ -26,6 +33,7 @@ import System.IO.Unsafe (unsafePerformIO)
|
|||||||
|
|
||||||
import PostgREST.Config (LogLevel (..))
|
import PostgREST.Config (LogLevel (..))
|
||||||
import PostgREST.Observation
|
import PostgREST.Observation
|
||||||
|
import PostgREST.Query (MainQuery (..))
|
||||||
|
|
||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
@@ -90,10 +98,9 @@ observationLogger loggerState logLevel obs = case obs of
|
|||||||
o@(HasqlPoolObs _) -> do
|
o@(HasqlPoolObs _) -> do
|
||||||
when (logLevel >= LogDebug) $ do
|
when (logLevel >= LogDebug) $ do
|
||||||
logWithZTime loggerState $ observationMessage o
|
logWithZTime loggerState $ observationMessage o
|
||||||
o@(DBQuery sql status) -> do
|
QueryObs gq status -> do
|
||||||
-- Does not log SQL when it's empty (for OPTIONS requests or for the default OpenAPI output)
|
when (shouldLogResponse logLevel status) $
|
||||||
when (sql /= mempty && shouldLogResponse logLevel status) $ do
|
logMainQ loggerState gq
|
||||||
logWithZTime loggerState $ observationMessage o
|
|
||||||
o@PoolRequest ->
|
o@PoolRequest ->
|
||||||
when (logLevel >= LogDebug) $ do
|
when (logLevel >= LogDebug) $ do
|
||||||
logWithZTime loggerState $ observationMessage o
|
logWithZTime loggerState $ observationMessage o
|
||||||
@@ -113,3 +120,20 @@ logWithZTime :: LoggerState -> Text -> IO ()
|
|||||||
logWithZTime loggerState txt = do
|
logWithZTime loggerState txt = do
|
||||||
zTime <- stateGetZTime loggerState
|
zTime <- stateGetZTime loggerState
|
||||||
hPutStrLn stderr $ toS (formatTime defaultTimeLocale "%d/%b/%Y:%T %z: " zTime) <> txt
|
hPutStrLn stderr $ toS (formatTime defaultTimeLocale "%d/%b/%Y:%T %z: " zTime) <> txt
|
||||||
|
|
||||||
|
logMainQ :: LoggerState -> MainQuery -> IO ()
|
||||||
|
logMainQ loggerState MainQuery{mqOpenAPI=(x, y, z),..} =
|
||||||
|
let snipts = renderSnippet <$> [mqMain, x, y, z]
|
||||||
|
-- Does not log SQL when it's empty (happens on OPTIONS requests and when the openapi queries are not generated)
|
||||||
|
logQ q = when (q /= mempty) $ logWithZTime loggerState $ showOnSingleLine '\n' $ T.decodeUtf8 q in
|
||||||
|
mapM_ logQ snipts
|
||||||
|
|
||||||
|
-- TODO: maybe patch upstream hasql-dynamic-statements so we have a less hackish way to convert
|
||||||
|
-- the SQL.Snippet or maybe don't use hasql-dynamic-statements and resort to plain strings for the queries and use regular hasql
|
||||||
|
renderSnippet :: SQL.Snippet -> ByteString
|
||||||
|
renderSnippet snippet =
|
||||||
|
let SQL.Statement sql _ _ _ = SQL.dynamicallyParameterized snippet decoder prepared
|
||||||
|
decoder = HD.noResult -- unused
|
||||||
|
prepared = False -- unused
|
||||||
|
in
|
||||||
|
sql
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ module PostgREST.Observation
|
|||||||
, ObsFatalError(..)
|
, ObsFatalError(..)
|
||||||
, observationMessage
|
, observationMessage
|
||||||
, ObservationHandler
|
, ObservationHandler
|
||||||
|
, showOnSingleLine
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
@@ -24,6 +25,7 @@ import Network.HTTP.Types.Status (Status)
|
|||||||
import Numeric (showFFloat)
|
import Numeric (showFFloat)
|
||||||
import PostgREST.Config.PgVersion
|
import PostgREST.Config.PgVersion
|
||||||
import qualified PostgREST.Error as Error
|
import qualified PostgREST.Error as Error
|
||||||
|
import PostgREST.Query (MainQuery)
|
||||||
|
|
||||||
import Protolude hiding (toList)
|
import Protolude hiding (toList)
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ data Observation
|
|||||||
| DBListenRetry Int
|
| DBListenRetry Int
|
||||||
| DBListenerGotSCacheMsg ByteString
|
| DBListenerGotSCacheMsg ByteString
|
||||||
| DBListenerGotConfigMsg ByteString
|
| DBListenerGotConfigMsg ByteString
|
||||||
| DBQuery ByteString Status
|
| QueryObs MainQuery Status
|
||||||
| ConfigReadErrorObs SQL.UsageError
|
| ConfigReadErrorObs SQL.UsageError
|
||||||
| ConfigInvalidObs Text
|
| ConfigInvalidObs Text
|
||||||
| ConfigSucceededObs
|
| ConfigSucceededObs
|
||||||
@@ -113,8 +115,8 @@ observationMessage = \case
|
|||||||
"Received a schema cache reload message on the " <> show channel <> " channel"
|
"Received a schema cache reload message on the " <> show channel <> " channel"
|
||||||
DBListenerGotConfigMsg channel ->
|
DBListenerGotConfigMsg channel ->
|
||||||
"Received a config reload message on the " <> show channel <> " channel"
|
"Received a config reload message on the " <> show channel <> " channel"
|
||||||
DBQuery sql _ ->
|
QueryObs{} ->
|
||||||
T.decodeUtf8 sql
|
mempty -- TODO pending refactor: The logic for printing the query cannot be done here. Join the observationMessage function into observationLogger to avoid this mempty.
|
||||||
ConfigReadErrorObs usageErr ->
|
ConfigReadErrorObs usageErr ->
|
||||||
"Failed to query database settings for the config parameters." <> jsonMessage usageErr
|
"Failed to query database settings for the config parameters." <> jsonMessage usageErr
|
||||||
QueryRoleSettingsErrorObs usageErr ->
|
QueryRoleSettingsErrorObs usageErr ->
|
||||||
@@ -155,11 +157,14 @@ observationMessage = \case
|
|||||||
|
|
||||||
jsonMessage err = T.decodeUtf8 . LBS.toStrict . Error.errorPayload $ Error.PgError False err
|
jsonMessage err = T.decodeUtf8 . LBS.toStrict . Error.errorPayload $ Error.PgError False err
|
||||||
|
|
||||||
showOnSingleLine txt = T.intercalate " " $ T.filter (/= '\t') <$> T.lines txt -- the errors from hasql-notifications come intercalated with "\t\n"
|
|
||||||
|
|
||||||
showListenerConnError :: SQL.ConnectionError -> Text
|
showListenerConnError :: SQL.ConnectionError -> Text
|
||||||
showListenerConnError = maybe "Connection error" (showOnSingleLine . T.decodeUtf8)
|
showListenerConnError = maybe "Connection error" (showOnSingleLine '\t' . T.decodeUtf8)
|
||||||
|
|
||||||
showListenerException :: Either SomeException () -> Text
|
showListenerException :: Either SomeException () -> Text
|
||||||
showListenerException (Right _) = "Failed getting notifications" -- should not happen as the listener will never finish (hasql-notifications uses `forever` internally) with a Right result
|
showListenerException (Right _) = "Failed getting notifications" -- should not happen as the listener will never finish (hasql-notifications uses `forever` internally) with a Right result
|
||||||
showListenerException (Left e) = showOnSingleLine $ show e
|
showListenerException (Left e) = showOnSingleLine '\t' $ show e
|
||||||
|
|
||||||
|
|
||||||
|
showOnSingleLine :: Char -> Text -> Text
|
||||||
|
showOnSingleLine split txt = T.intercalate " " $ T.filter (/= split) <$> T.lines txt -- the errors from hasql-notifications come intercalated with "\t\n"
|
||||||
|
|||||||
+54
-40
@@ -7,13 +7,15 @@ Description : PostgREST query executor
|
|||||||
This module parametrizes, prepares, executes SQL queries and decodes their results.
|
This module parametrizes, prepares, executes SQL queries and decodes their results.
|
||||||
|
|
||||||
TODO: This module shouldn't depend on SchemaCache: once OpenAPI is removed, this can be done
|
TODO: This module shouldn't depend on SchemaCache: once OpenAPI is removed, this can be done
|
||||||
|
TOOD: Split the SQL transaction concerns module into another one so Query.hs is pure
|
||||||
-}
|
-}
|
||||||
module PostgREST.Query
|
module PostgREST.Query
|
||||||
( Query (..)
|
( Query (..)
|
||||||
, QueryResult (..)
|
, QueryResult (..)
|
||||||
, ResultSet (..)
|
, ResultSet (..)
|
||||||
, query
|
, mainTx
|
||||||
, getSQLQuery
|
, mainQuery
|
||||||
|
, MainQuery (..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Lens ((^?))
|
import Control.Lens ((^?))
|
||||||
@@ -28,7 +30,6 @@ import qualified Hasql.Decoders as HD
|
|||||||
import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql)
|
import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql)
|
||||||
import qualified Hasql.DynamicStatements.Statement as SQL
|
import qualified Hasql.DynamicStatements.Statement as SQL
|
||||||
import qualified Hasql.Session as SQL (Session)
|
import qualified Hasql.Session as SQL (Session)
|
||||||
import qualified Hasql.Statement as SQL
|
|
||||||
import qualified Hasql.Transaction as SQL
|
import qualified Hasql.Transaction as SQL
|
||||||
import qualified Hasql.Transaction.Sessions as SQL
|
import qualified Hasql.Transaction.Sessions as SQL
|
||||||
|
|
||||||
@@ -73,7 +74,6 @@ data Query
|
|||||||
, dqTxMode :: SQL.Mode
|
, dqTxMode :: SQL.Mode
|
||||||
, dqDbHandler :: DbHandler QueryResult
|
, dqDbHandler :: DbHandler QueryResult
|
||||||
, dqTransaction :: SQL.IsolationLevel -> SQL.Mode -> SQL.Transaction (Either Error QueryResult) -> SQL.Session (Either Error QueryResult)
|
, dqTransaction :: SQL.IsolationLevel -> SQL.Mode -> SQL.Transaction (Either Error QueryResult) -> SQL.Session (Either Error QueryResult)
|
||||||
, dqSQL :: ByteString
|
|
||||||
}
|
}
|
||||||
| NoDbQuery QueryResult
|
| NoDbQuery QueryResult
|
||||||
|
|
||||||
@@ -83,6 +83,16 @@ data QueryResult
|
|||||||
| MaybeDbResult InspectPlan (Maybe (TablesMap, RoutineMap, Maybe Text))
|
| MaybeDbResult InspectPlan (Maybe (TablesMap, RoutineMap, Maybe Text))
|
||||||
| NoDbResult InfoPlan
|
| NoDbResult InfoPlan
|
||||||
|
|
||||||
|
-- The Queries that run on every request
|
||||||
|
data MainQuery = MainQuery
|
||||||
|
{ mqTxVars :: SQL.Snippet -- ^ the transaction variables that always run on each query
|
||||||
|
, mqPreReq :: Maybe SQL.Snippet -- ^ the pre-request function that runs if enabled
|
||||||
|
, mqCount :: SQL.Snippet -- ^ this count query is actually a fragment of the main query, but the same count query also runs after the main one in the case of `count=estimated`, so it's cached here.
|
||||||
|
-- TODO only one of the following queries actually runs on each request, once OpenAPI is removed from core it will be easier to refactor this
|
||||||
|
, mqMain :: SQL.Snippet
|
||||||
|
, mqOpenAPI :: (SQL.Snippet, SQL.Snippet, SQL.Snippet)
|
||||||
|
}
|
||||||
|
|
||||||
-- | Standard result set format used for all queries
|
-- | Standard result set format used for all queries
|
||||||
data ResultSet
|
data ResultSet
|
||||||
= RSStandard
|
= RSStandard
|
||||||
@@ -104,21 +114,21 @@ data ResultSet
|
|||||||
}
|
}
|
||||||
| RSPlan BS.ByteString -- ^ the plan of the query
|
| RSPlan BS.ByteString -- ^ the plan of the query
|
||||||
|
|
||||||
query :: AppConfig -> AuthResult -> ApiRequest -> ActionPlan -> SchemaCache -> Query
|
mainTx :: MainQuery -> AppConfig -> AuthResult -> ApiRequest -> ActionPlan -> SchemaCache -> Query
|
||||||
query _ _ _ (NoDb x) _ = NoDbQuery $ NoDbResult x
|
mainTx _ _ _ _ (NoDb x) _ = NoDbQuery $ NoDbResult x
|
||||||
query conf@AppConfig{..} auth@AuthResult{..} apiReq (Db plan) sCache =
|
mainTx genQ@MainQuery{..} conf@AppConfig{..} AuthResult{..} apiReq (Db plan) sCache =
|
||||||
DbQuery isoLvl txMode dbHandler transaction mainSQLQuery
|
DbQuery isoLvl txMode dbHandler transaction
|
||||||
where
|
where
|
||||||
transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction
|
transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction
|
||||||
isoLvl = planIsoLvl conf authRole plan
|
isoLvl = planIsoLvl conf authRole plan
|
||||||
txMode = planTxMode plan
|
txMode = planTxMode plan
|
||||||
(mainActionQuery, mainSQLQuery) = actionQuery plan conf apiReq sCache
|
mainActionQuery = actionQuery genQ plan conf apiReq sCache
|
||||||
dbHandler = do
|
dbHandler = do
|
||||||
lift $ SQL.statement mempty $ SQL.dynamicallyParameterized
|
lift $ SQL.statement mempty $ SQL.dynamicallyParameterized mqTxVars
|
||||||
(PreQuery.txVarQuery plan conf auth apiReq)
|
HD.noResult configDbPreparedStatements
|
||||||
|
lift $ whenJust mqPreReq $ \q ->
|
||||||
|
SQL.statement mempty $ SQL.dynamicallyParameterized q
|
||||||
HD.noResult configDbPreparedStatements
|
HD.noResult configDbPreparedStatements
|
||||||
lift $ whenJust configDbPreRequest $ \prereq -> do
|
|
||||||
SQL.statement mempty $ SQL.dynamicallyParameterized (PreQuery.preReqQuery prereq) HD.noResult configDbPreparedStatements
|
|
||||||
mainActionQuery
|
mainActionQuery
|
||||||
|
|
||||||
planTxMode :: DbActionPlan -> SQL.Mode
|
planTxMode :: DbActionPlan -> SQL.Mode
|
||||||
@@ -133,32 +143,42 @@ planIsoLvl AppConfig{configRoleIsoLvl} role actPlan = case actPlan of
|
|||||||
where
|
where
|
||||||
roleIsoLvl = HM.findWithDefault SQL.ReadCommitted role configRoleIsoLvl
|
roleIsoLvl = HM.findWithDefault SQL.ReadCommitted role configRoleIsoLvl
|
||||||
|
|
||||||
|
mainQuery :: ActionPlan -> AppConfig -> ApiRequest -> AuthResult -> Maybe QualifiedIdentifier -> MainQuery
|
||||||
|
mainQuery (NoDb _) _ _ _ _ = MainQuery mempty Nothing mempty mempty (mempty, mempty, mempty)
|
||||||
|
mainQuery (Db plan) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} authRes preReq =
|
||||||
|
let genQ = MainQuery (PreQuery.txVarQuery plan conf authRes apiReq) (PreQuery.preReqQuery <$> preReq) in
|
||||||
|
case plan of
|
||||||
|
DbCrud WrappedReadPlan{..} ->
|
||||||
|
let countQuery = QueryBuilder.readPlanToCountQuery wrReadPlan in
|
||||||
|
genQ countQuery (Statements.mainRead wrReadPlan countQuery preferCount configDbMaxRows wrMedia wrHandler) (mempty, mempty, mempty)
|
||||||
|
DbCrud MutateReadPlan{..} ->
|
||||||
|
genQ mempty (Statements.mainWrite mrReadPlan mrMutatePlan mrMedia mrHandler preferRepresentation preferResolution) (mempty, mempty, mempty)
|
||||||
|
DbCall CallReadPlan{..} ->
|
||||||
|
genQ mempty (Statements.mainCall crProc crCallPlan crReadPlan preferCount crMedia crHandler) (mempty, mempty, mempty)
|
||||||
|
MayUseDb InspectPlan{ipSchema=tSchema} ->
|
||||||
|
genQ mempty mempty (SqlFragment.accessibleTables tSchema, SqlFragment.accessibleFuncs tSchema, SqlFragment.schemaDescription tSchema)
|
||||||
|
|
||||||
-- TODO: Generate the Hasql Statement in a diferent module after the OpenAPI functionality is removed
|
-- TODO: Generate the Hasql Statement in a diferent module after the OpenAPI functionality is removed
|
||||||
actionQuery :: DbActionPlan -> AppConfig -> ApiRequest -> SchemaCache -> (DbHandler QueryResult, ByteString)
|
actionQuery :: MainQuery -> DbActionPlan -> AppConfig -> ApiRequest -> SchemaCache -> DbHandler QueryResult
|
||||||
actionQuery (DbCrud plan@WrappedReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ =
|
actionQuery MainQuery{..} (DbCrud plan@WrappedReadPlan{..}) conf@AppConfig{..} apiReq _ =
|
||||||
(mainActionQuery, mainSQLQuery)
|
mainActionQuery
|
||||||
where
|
where
|
||||||
countQuery = QueryBuilder.readPlanToCountQuery wrReadPlan
|
result = SQL.dynamicallyParameterized mqMain decodeIt configDbPreparedStatements
|
||||||
result@(SQL.Statement mainSQLQuery _ _ _) = SQL.dynamicallyParameterized
|
|
||||||
(Statements.mainRead wrReadPlan countQuery preferCount configDbMaxRows wrMedia wrHandler)
|
|
||||||
decodeIt configDbPreparedStatements
|
|
||||||
mainActionQuery = do
|
mainActionQuery = do
|
||||||
resultSet <- lift $ SQL.statement mempty result
|
resultSet <- lift $ SQL.statement mempty result
|
||||||
failNotSingular wrMedia resultSet
|
failNotSingular wrMedia resultSet
|
||||||
optionalRollback conf apiReq
|
optionalRollback conf apiReq
|
||||||
DbCrudResult plan <$> resultSetWTotal conf apiReq resultSet countQuery
|
DbCrudResult plan <$> resultSetWTotal conf apiReq resultSet mqCount
|
||||||
|
|
||||||
decodeIt :: HD.Result ResultSet
|
decodeIt :: HD.Result ResultSet
|
||||||
decodeIt = case wrMedia of
|
decodeIt = case wrMedia of
|
||||||
MTVndPlan{} -> planRow
|
MTVndPlan{} -> planRow
|
||||||
_ -> HD.singleRow $ standardRow True
|
_ -> HD.singleRow $ standardRow True
|
||||||
|
|
||||||
actionQuery (DbCrud plan@MutateReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ =
|
actionQuery MainQuery{..} (DbCrud plan@MutateReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ =
|
||||||
(mainActionQuery, mainSQLQuery)
|
mainActionQuery
|
||||||
where
|
where
|
||||||
result@(SQL.Statement mainSQLQuery _ _ _) = SQL.dynamicallyParameterized
|
result = SQL.dynamicallyParameterized mqMain decodeIt configDbPreparedStatements
|
||||||
(Statements.mainWrite mrReadPlan mrMutatePlan mrMedia mrHandler preferRepresentation preferResolution)
|
|
||||||
decodeIt configDbPreparedStatements
|
|
||||||
failMutation resultSet = case mrMutation of
|
failMutation resultSet = case mrMutation of
|
||||||
MutationCreate -> do
|
MutationCreate -> do
|
||||||
failNotSingular mrMedia resultSet
|
failNotSingular mrMedia resultSet
|
||||||
@@ -181,12 +201,10 @@ actionQuery (DbCrud plan@MutateReadPlan{..}) conf@AppConfig{..} apiReq@ApiReques
|
|||||||
MTVndPlan{} -> planRow
|
MTVndPlan{} -> planRow
|
||||||
_ -> fromMaybe (RSStandard Nothing 0 mempty mempty Nothing Nothing Nothing) <$> HD.rowMaybe (standardRow False)
|
_ -> fromMaybe (RSStandard Nothing 0 mempty mempty Nothing Nothing Nothing) <$> HD.rowMaybe (standardRow False)
|
||||||
|
|
||||||
actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ =
|
actionQuery MainQuery{..} (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{iPreferences=Preferences{..}} _ =
|
||||||
(mainActionQuery, mainSQLQuery)
|
mainActionQuery
|
||||||
where
|
where
|
||||||
result@(SQL.Statement mainSQLQuery _ _ _) = SQL.dynamicallyParameterized
|
result = SQL.dynamicallyParameterized mqMain decodeIt configDbPreparedStatements
|
||||||
(Statements.mainCall crProc crCallPlan crReadPlan preferCount crMedia crHandler)
|
|
||||||
decodeIt configDbPreparedStatements
|
|
||||||
|
|
||||||
mainActionQuery = do
|
mainActionQuery = do
|
||||||
resultSet <- lift $ SQL.statement mempty result
|
resultSet <- lift $ SQL.statement mempty result
|
||||||
@@ -200,15 +218,15 @@ actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{
|
|||||||
MTVndPlan{} -> planRow
|
MTVndPlan{} -> planRow
|
||||||
_ -> fromMaybe (RSStandard (Just 0) 0 mempty mempty Nothing Nothing Nothing) <$> HD.rowMaybe (standardRow True)
|
_ -> fromMaybe (RSStandard (Just 0) 0 mempty mempty Nothing Nothing Nothing) <$> HD.rowMaybe (standardRow True)
|
||||||
|
|
||||||
actionQuery (MayUseDb plan@InspectPlan{ipSchema=tSchema}) AppConfig{..} _ sCache =
|
actionQuery MainQuery{mqOpenAPI=(tblsQ, funcsQ, schQ)} (MayUseDb plan@InspectPlan{ipSchema=tSchema}) AppConfig{..} _ sCache =
|
||||||
(mainActionQuery, mempty)
|
mainActionQuery
|
||||||
where
|
where
|
||||||
mainActionQuery = lift $
|
mainActionQuery = lift $
|
||||||
case configOpenApiMode of
|
case configOpenApiMode of
|
||||||
OAFollowPriv -> do
|
OAFollowPriv -> do
|
||||||
tableAccess <- SQL.statement mempty $ SQL.dynamicallyParameterized (SqlFragment.accessibleTables tSchema) decodeAccessibleIdentifiers configDbPreparedStatements
|
tableAccess <- SQL.statement mempty $ SQL.dynamicallyParameterized tblsQ decodeAccessibleIdentifiers configDbPreparedStatements
|
||||||
accFuncs <- SQL.statement mempty $ SQL.dynamicallyParameterized (SqlFragment.accessibleFuncs tSchema) SchemaCache.decodeFuncs configDbPreparedStatements
|
accFuncs <- SQL.statement mempty $ SQL.dynamicallyParameterized funcsQ SchemaCache.decodeFuncs configDbPreparedStatements
|
||||||
schDesc <- SQL.statement mempty $ SQL.dynamicallyParameterized (SqlFragment.schemaDescription tSchema) decodeSchemaDesc configDbPreparedStatements
|
schDesc <- SQL.statement mempty $ SQL.dynamicallyParameterized schQ decodeSchemaDesc configDbPreparedStatements
|
||||||
let tbls = HM.filterWithKey (\qi _ -> S.member qi tableAccess) $ SchemaCache.dbTables sCache
|
let tbls = HM.filterWithKey (\qi _ -> S.member qi tableAccess) $ SchemaCache.dbTables sCache
|
||||||
|
|
||||||
pure $ MaybeDbResult plan (Just (tbls, accFuncs, schDesc))
|
pure $ MaybeDbResult plan (Just (tbls, accFuncs, schDesc))
|
||||||
@@ -304,10 +322,6 @@ optionalRollback AppConfig{..} ApiRequest{iPreferences=Preferences{..}} = do
|
|||||||
shouldRollback =
|
shouldRollback =
|
||||||
preferTransaction == Just Rollback
|
preferTransaction == Just Rollback
|
||||||
|
|
||||||
getSQLQuery :: Query -> ByteString
|
|
||||||
getSQLQuery DbQuery{dqSQL} = dqSQL
|
|
||||||
getSQLQuery _ = mempty
|
|
||||||
|
|
||||||
-- | We use rowList because when doing EXPLAIN (FORMAT TEXT), the result comes as many rows. FORMAT JSON comes as one.
|
-- | We use rowList because when doing EXPLAIN (FORMAT TEXT), the result comes as many rows. FORMAT JSON comes as one.
|
||||||
planRow :: HD.Result ResultSet
|
planRow :: HD.Result ResultSet
|
||||||
planRow = RSPlan . BS.unlines <$> HD.rowList (column HD.bytea)
|
planRow = RSPlan . BS.unlines <$> HD.rowList (column HD.bytea)
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
import PostgREST.Auth.Types (AuthResult (..))
|
|
||||||
import PostgREST.ApiRequest (ApiRequest (..))
|
import PostgREST.ApiRequest (ApiRequest (..))
|
||||||
import PostgREST.ApiRequest.Preferences (PreferTimezone (..),
|
import PostgREST.ApiRequest.Preferences (PreferTimezone (..),
|
||||||
Preferences (..))
|
Preferences (..))
|
||||||
|
import PostgREST.Auth.Types (AuthResult (..))
|
||||||
import PostgREST.Config (AppConfig (..))
|
import PostgREST.Config (AppConfig (..))
|
||||||
import PostgREST.Plan (CallReadPlan (..),
|
import PostgREST.Plan (CallReadPlan (..),
|
||||||
DbActionPlan (..))
|
DbActionPlan (..))
|
||||||
|
|||||||
+16
-16
@@ -1033,13 +1033,7 @@ def test_log_level(level, defaultenv):
|
|||||||
def test_log_query(level, defaultenv):
|
def test_log_query(level, defaultenv):
|
||||||
"log_query=true should log the SQL query according to the log_level"
|
"log_query=true should log the SQL query according to the log_level"
|
||||||
|
|
||||||
env = {
|
env = {**defaultenv, "PGRST_LOG_LEVEL": level, "PGRST_LOG_QUERY": "main-query"}
|
||||||
**defaultenv,
|
|
||||||
"PGRST_LOG_LEVEL": level,
|
|
||||||
"PGRST_LOG_QUERY": "main-query",
|
|
||||||
# The root path can only log SQL when a function is set in db-root-spec
|
|
||||||
"PGRST_DB_ROOT_SPEC": "root",
|
|
||||||
}
|
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
with run(env=env) as postgrest:
|
||||||
response = postgrest.session.get("/")
|
response = postgrest.session.get("/")
|
||||||
@@ -1051,7 +1045,9 @@ def test_log_query(level, defaultenv):
|
|||||||
response = postgrest.session.get("/infinite_recursion")
|
response = postgrest.session.get("/infinite_recursion")
|
||||||
assert response.status_code == 500
|
assert response.status_code == 500
|
||||||
|
|
||||||
root_2xx_regx = r'.+: WITH pgrst_source AS.+SELECT "public"\."root"\(\) pgrst_scalar.+_postgrest_t'
|
root_2xx_regx_ln1 = r".+: SELECT n.nspname AS table_schema, .+ FROM pg_class c .+ ORDER BY table_schema, table_name"
|
||||||
|
root_2xx_regx_ln2 = r".+: WITH base_types AS \(.+\) SELECT pn.nspname AS proc_schema, .+ FROM pg_proc p.+AND p.pronamespace = \$1::regnamespace"
|
||||||
|
root_2xx_regx_ln3 = r".+: SELECT pg_catalog\.obj_description\(\$1::regnamespace, 'pg_namespace'\)"
|
||||||
get_2xx_regx = r'.+: WITH pgrst_source AS.+SELECT "public"\."projects"\.\* FROM "public"\."projects".+_postgrest_t'
|
get_2xx_regx = r'.+: WITH pgrst_source AS.+SELECT "public"\."projects"\.\* FROM "public"\."projects".+_postgrest_t'
|
||||||
infinite_recursion_5xx_regx = r'.+: WITH pgrst_source AS.+SELECT "public"\."infinite_recursion"\.\* FROM "public"\."infinite_recursion".+_postgrest_t'
|
infinite_recursion_5xx_regx = r'.+: WITH pgrst_source AS.+SELECT "public"\."infinite_recursion"\.\* FROM "public"\."infinite_recursion".+_postgrest_t'
|
||||||
|
|
||||||
@@ -1067,15 +1063,19 @@ def test_log_query(level, defaultenv):
|
|||||||
assert re.match(infinite_recursion_5xx_regx, output[1])
|
assert re.match(infinite_recursion_5xx_regx, output[1])
|
||||||
assert len(output) == 2
|
assert len(output) == 2
|
||||||
elif level == "info":
|
elif level == "info":
|
||||||
output = postgrest.read_stdout(nlines=6)
|
output = postgrest.read_stdout(nlines=8)
|
||||||
assert re.match(root_2xx_regx, output[0])
|
assert re.match(root_2xx_regx_ln1, output[0])
|
||||||
assert re.match(get_2xx_regx, output[2])
|
assert re.match(root_2xx_regx_ln2, output[1])
|
||||||
assert re.match(infinite_recursion_5xx_regx, output[5])
|
assert re.match(root_2xx_regx_ln3, output[2])
|
||||||
assert len(output) == 6
|
assert re.match(get_2xx_regx, output[4])
|
||||||
|
assert re.match(infinite_recursion_5xx_regx, output[7])
|
||||||
|
assert len(output) == 8
|
||||||
elif level == "debug":
|
elif level == "debug":
|
||||||
output_root = postgrest.read_stdout(nlines=6)
|
output_root = postgrest.read_stdout(nlines=8)
|
||||||
assert re.match(root_2xx_regx, output_root[4])
|
assert re.match(root_2xx_regx_ln1, output_root[4])
|
||||||
assert len(output_root) == 6
|
assert re.match(root_2xx_regx_ln2, output_root[5])
|
||||||
|
assert re.match(root_2xx_regx_ln3, output_root[6])
|
||||||
|
assert len(output_root) == 8
|
||||||
output_get = postgrest.read_stdout(nlines=6)
|
output_get = postgrest.read_stdout(nlines=6)
|
||||||
assert re.match(get_2xx_regx, output_get[4])
|
assert re.match(get_2xx_regx, output_get[4])
|
||||||
assert len(output_get) == 6
|
assert len(output_get) == 6
|
||||||
|
|||||||
Reference in New Issue
Block a user