diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eb32df642..7293783821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #504, Add `log-level` config option. The admitted levels are: crit, error, warn and info - @steve-chavez - #1607, Enable embedding through multiple views recursively - @wolfgangwalther - #1598, Allow rollback of the transaction with Prefer tx=rollback - @wolfgangwalther - - #1633, Enable prepared statements for filters. When behind a connection pooler, you can disable preparing with `db-prepared-statements=false` - @steve-chavez + - #1633, #1600, Enable prepared statements for filters. When behind a connection pooler, you can disable preparing with `db-prepared-statements=false` - @steve-chavez ### Fixed diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index 2b1cc1d70a..844b65d1c3 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -8,16 +8,21 @@ Description : Sets CORS policy. Also the PostgreSQL GUCs, role, search_path and module PostgREST.Middleware where -import qualified Data.Aeson as JSON -import qualified Data.ByteString.Char8 as BS -import qualified Data.CaseInsensitive as CI -import Data.Function (id) -import qualified Data.HashMap.Strict as M -import Data.List (lookup) -import Data.Scientific (FPFormat (..), - formatScientific, isInteger) -import qualified Data.Text as T -import qualified Hasql.Transaction as H +import qualified Hasql.Decoders as HD +import qualified Hasql.DynamicStatements.Statement as H +import PostgREST.Private.Common + +import qualified Data.Aeson as JSON +import qualified Data.ByteString.Char8 as BS +import qualified Data.CaseInsensitive as CI +import Data.Function (id) +import qualified Data.HashMap.Strict as M +import Data.List (lookup) +import Data.Scientific (FPFormat (..), + formatScientific, + isInteger) +import qualified Data.Text as T +import qualified Hasql.Transaction as H import Network.HTTP.Types.Status (Status, status400, status500, statusCode) import Network.Wai.Logger (showSockAddr) @@ -43,7 +48,9 @@ runPgLocals :: AppConfig -> M.HashMap Text JSON.Value -> (ApiRequest -> H.Transaction Response) -> ApiRequest -> H.Transaction Response runPgLocals conf claims app req = do - H.sql . toS $ "select " <> T.intercalate ", " (searchPathSql : roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql) + H.statement mempty $ H.dynamicallyParameterized + ("select " <> intercalateSnippet ", " (searchPathSql : roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql)) + HD.noResult (configDbPreparedStatements conf) traverse_ H.sql preReqSql app req where diff --git a/src/PostgREST/Private/Common.hs b/src/PostgREST/Private/Common.hs index 006c4e68c9..c068fb6184 100644 --- a/src/PostgREST/Private/Common.hs +++ b/src/PostgREST/Private/Common.hs @@ -5,10 +5,13 @@ Description : Common helper functions. module PostgREST.Private.Common where import Data.Maybe -import qualified Hasql.Decoders as HD -import qualified Hasql.Encoders as HE +import qualified Hasql.Decoders as HD +import qualified Hasql.DynamicStatements.Snippet as H +import qualified Hasql.Encoders as HE import Protolude +import Data.Foldable (foldr1) + column :: HD.Value a -> HD.Row a column = HD.column . HD.nonNullable @@ -23,3 +26,10 @@ param = HE.param . HE.nonNullable arrayParam :: HE.Value a -> HE.Params [a] arrayParam = param . HE.array . HE.dimension foldl' . HE.element . HE.nonNullable + +emptySnippetOnFalse :: H.Snippet -> Bool -> H.Snippet +emptySnippetOnFalse val cond = if cond then mempty else val + +intercalateSnippet :: ByteString -> [H.Snippet] -> H.Snippet +intercalateSnippet _ [] = mempty +intercalateSnippet frag snippets = foldr1 (\a b -> a <> H.sql frag <> b) snippets diff --git a/src/PostgREST/Private/QueryFragment.hs b/src/PostgREST/Private/QueryFragment.hs index c987380df2..36b8eccf11 100644 --- a/src/PostgREST/Private/QueryFragment.hs +++ b/src/PostgREST/Private/QueryFragment.hs @@ -30,9 +30,8 @@ import Protolude hiding (cast, import Protolude.Conv (toS) import Text.InterpolatedString.Perl6 (qc) -import qualified Hasql.Encoders as HE - -import Data.Foldable (foldr1) +import qualified Hasql.Encoders as HE +import PostgREST.Private.Common noLocationF :: SqlFragment noLocationF = "array[]::text[]" @@ -250,10 +249,3 @@ unknownEncoder = H.encoderAndParam (HE.nonNullable HE.unknown) unknownLiteral :: Text -> H.Snippet unknownLiteral = unknownEncoder . encodeUtf8 - -emptySnippetOnFalse :: H.Snippet -> Bool -> H.Snippet -emptySnippetOnFalse val cond = if cond then mempty else val - -intercalateSnippet :: SqlFragment -> [H.Snippet] -> H.Snippet -intercalateSnippet _ [] = mempty -intercalateSnippet frag snippets = foldr1 (\a b -> a <> H.sql frag <> b) snippets diff --git a/src/PostgREST/QueryBuilder.hs b/src/PostgREST/QueryBuilder.hs index 63de005b77..13f75c69ba 100644 --- a/src/PostgREST/QueryBuilder.hs +++ b/src/PostgREST/QueryBuilder.hs @@ -26,6 +26,7 @@ import qualified Hasql.DynamicStatements.Snippet as H import Data.Tree (Tree (..)) import Data.Maybe +import PostgREST.Private.Common import PostgREST.Private.QueryFragment import PostgREST.Types import Protolude hiding (cast, intercalate, @@ -173,6 +174,6 @@ limitedQuery :: H.Snippet -> Maybe Integer -> H.Snippet limitedQuery query maxRows = query <> H.sql (maybe mempty (\x -> " LIMIT " <> BS.pack (show x)) maxRows) -- | Do a pg set_config(setting, value, true) call. This is equivalent to a SET LOCAL. -setConfigLocal :: Text -> (Text, Text) -> Text +setConfigLocal :: Text -> (Text, Text) -> H.Snippet setConfigLocal prefix (k, v) = - "set_config(" <> decodeUtf8 (pgFmtLit (prefix <> k)) <> ", " <> decodeUtf8 (pgFmtLit v) <> ", true)" + "set_config(" <> unknownLiteral (prefix <> k) <> ", " <> unknownLiteral v <> ", true)"