Skip to content

Commit

Permalink
feat: add config to disable Prefer: count=exact
Browse files Browse the repository at this point in the history
  • Loading branch information
taimoorzaeem committed Oct 2, 2023
1 parent 4c44782 commit d7ad57e
Show file tree
Hide file tree
Showing 19 changed files with 69 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- #2771, Add `Server-Timing` header with JWT duration - @taimoorzaeem
- #2698, Add config `jwt-cache-max-lifetime` and implement JWT caching - @taimoorzaeem
- #2943, Add `handling=strict/lenient` for Prefer header - @taimoorzaeem
- #2777, Add config `db-exact-count-enable` to disable `Prefer: count=exact` - @taimoorzaeem

### Fixed

Expand Down
1 change: 1 addition & 0 deletions postgrest.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ test-suite spec
Feature.Query.AndOrParamsSpec
Feature.Query.ComputedRelsSpec
Feature.Query.DeleteSpec
Feature.Query.DisableExactCountSpec
Feature.Query.EmbedDisambiguationSpec
Feature.Query.EmbedInnerJoinSpec
Feature.Query.ErrorSpec
Expand Down
2 changes: 1 addition & 1 deletion src/PostgREST/ApiRequest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ userApiRequest conf req reqBody = do
, iRange = ranges
, iTopLevelRange = topLevelRange
, iPayload = payload
, iPreferences = Preferences.fromHeaders (configDbTxAllowOverride conf) hdrs
, iPreferences = Preferences.fromHeaders (configDbTxAllowOverride conf, configDbExactCountEnable conf) hdrs
, iQueryParams = qPrms
, iColumns = columns
, iHeaders = iHdrs
Expand Down
22 changes: 13 additions & 9 deletions src/PostgREST/ApiRequest/Preferences.hs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ data Preferences
--
-- One header with comma-separated values can be used to set multiple preferences:
--
-- >>> pPrint $ fromHeaders True [("Prefer", "resolution=ignore-duplicates, count=exact")]
-- >>> pPrint $ fromHeaders (True,True) [("Prefer", "resolution=ignore-duplicates, count=exact")]
-- Preferences
-- { preferResolution = Just IgnoreDuplicates
-- , preferRepresentation = Nothing
Expand All @@ -71,7 +71,7 @@ data Preferences
--
-- Multiple headers can also be used:
--
-- >>> pPrint $ fromHeaders True [("Prefer", "resolution=ignore-duplicates"), ("Prefer", "count=exact"), ("Prefer", "missing=null"), ("Prefer", "handling=lenient"), ("Prefer", "invalid")]
-- >>> pPrint $ fromHeaders (True,True) [("Prefer", "resolution=ignore-duplicates"), ("Prefer", "count=exact"), ("Prefer", "missing=null"), ("Prefer", "handling=lenient"), ("Prefer", "invalid")]
-- Preferences
-- { preferResolution = Just IgnoreDuplicates
-- , preferRepresentation = Nothing
Expand All @@ -85,13 +85,13 @@ data Preferences
--
-- If a preference is set more than once, only the first is used:
--
-- >>> preferTransaction $ fromHeaders True [("Prefer", "tx=commit, tx=rollback")]
-- >>> preferTransaction $ fromHeaders (True,True) [("Prefer", "tx=commit, tx=rollback")]
-- Just Commit
--
-- This is also the case across multiple headers:
--
-- >>> :{
-- preferResolution . fromHeaders True $
-- preferResolution . fromHeaders (True,True) $
-- [ ("Prefer", "resolution=ignore-duplicates")
-- , ("Prefer", "resolution=merge-duplicates")
-- ]
Expand All @@ -101,7 +101,7 @@ data Preferences
--
-- Preferences can be separated by arbitrary amounts of space, lower-case header is also recognized:
--
-- >>> pPrint $ fromHeaders True [("prefer", "count=exact, tx=commit ,return=representation , missing=default, handling=strict, anything")]
-- >>> pPrint $ fromHeaders (True,True) [("prefer", "count=exact, tx=commit ,return=representation , missing=default, handling=strict, anything")]
-- Preferences
-- { preferResolution = Nothing
-- , preferRepresentation = Just Full
Expand All @@ -113,14 +113,18 @@ data Preferences
-- , invalidPrefs = [ "anything" ]
-- }
--
fromHeaders :: Bool -> [HTTP.Header] -> Preferences
fromHeaders allowTxEndOverride headers =
fromHeaders :: (Bool,Bool) -> [HTTP.Header] -> Preferences
fromHeaders (dbAllowTxOverride,exactCountEnable) headers =
Preferences
{ preferResolution = parsePrefs [MergeDuplicates, IgnoreDuplicates]
, preferRepresentation = parsePrefs [Full, None, HeadersOnly]
, preferParameters = parsePrefs [SingleObject]
, preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount]
, preferTransaction = if allowTxEndOverride then parsePrefs [Commit, Rollback] else Nothing
, preferCount = if exactCountEnable
then parsePrefs [ExactCount, PlannedCount, EstimatedCount]
else parsePrefs [PlannedCount, EstimatedCount]
, preferTransaction = if dbAllowTxOverride
then parsePrefs [Commit, Rollback]
else Nothing
, preferMissing = parsePrefs [ApplyDefaults, ApplyNulls]
, preferHandling = parsePrefs [Strict, Lenient]
, invalidPrefs = filter (`notElem` acceptedPrefs) prefs
Expand Down
3 changes: 3 additions & 0 deletions src/PostgREST/CLI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ exampleConfigFile =
|## Function for in-database configuration
|## db-pre-config = "postgrest.pre_config"
|
|## Enable or Disable `Prefer: count=exact`
|# db-exact-count-enable = true
|
|## Extra schemas to add to the search_path of every request
|db-extra-search-path = "public"
|
Expand Down
3 changes: 3 additions & 0 deletions src/PostgREST/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ data AppConfig = AppConfig
, configDbAnonRole :: Maybe BS.ByteString
, configDbChannel :: Text
, configDbChannelEnabled :: Bool
, configDbExactCountEnable :: Bool
, configDbExtraSearchPath :: [Text]
, configDbMaxRows :: Maybe Integer
, configDbPlanEnabled :: Bool
Expand Down Expand Up @@ -142,6 +143,7 @@ toText conf =
[("db-anon-role", q . T.decodeUtf8 . fromMaybe "" . configDbAnonRole)
,("db-channel", q . configDbChannel)
,("db-channel-enabled", T.toLower . show . configDbChannelEnabled)
,("db-exact-count-enable", T.toLower . show . configDbExactCountEnable)
,("db-extra-search-path", q . T.intercalate "," . configDbExtraSearchPath)
,("db-max-rows", maybe "\"\"" show . configDbMaxRows)
,("db-plan-enabled", T.toLower . show . configDbPlanEnabled)
Expand Down Expand Up @@ -236,6 +238,7 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
<*> (fmap encodeUtf8 <$> optString "db-anon-role")
<*> (fromMaybe "pgrst" <$> optString "db-channel")
<*> (fromMaybe True <$> optBool "db-channel-enabled")
<*> (fromMaybe True <$> optBool "db-exact-count-enable")
<*> (maybe ["public"] splitOnCommas <$> optValue "db-extra-search-path")
<*> optWithAlias (optInt "db-max-rows")
(optInt "max-rows")
Expand Down
1 change: 1 addition & 0 deletions test/io/configs/expected/aliases.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
db-anon-role = ""
db-channel = "pgrst"
db-channel-enabled = true
db-exact-count-enable = true
db-extra-search-path = "public"
db-max-rows = 1000
db-plan-enabled = false
Expand Down
1 change: 1 addition & 0 deletions test/io/configs/expected/boolean-numeric.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
db-anon-role = ""
db-channel = "pgrst"
db-channel-enabled = true
db-exact-count-enable = true
db-extra-search-path = "public"
db-max-rows = ""
db-plan-enabled = false
Expand Down
1 change: 1 addition & 0 deletions test/io/configs/expected/boolean-string.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
db-anon-role = ""
db-channel = "pgrst"
db-channel-enabled = true
db-exact-count-enable = true
db-extra-search-path = "public"
db-max-rows = ""
db-plan-enabled = false
Expand Down
1 change: 1 addition & 0 deletions test/io/configs/expected/defaults.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
db-anon-role = ""
db-channel = "pgrst"
db-channel-enabled = true
db-exact-count-enable = true
db-extra-search-path = "public"
db-max-rows = ""
db-plan-enabled = false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
db-anon-role = "pre_config_role"
db-channel = "postgrest"
db-channel-enabled = false
db-exact-count-enable = false
db-extra-search-path = "public,extensions,other"
db-max-rows = 100
db-plan-enabled = true
Expand Down
1 change: 1 addition & 0 deletions test/io/configs/expected/no-defaults-with-db.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
db-anon-role = "anonymous"
db-channel = "postgrest"
db-channel-enabled = false
db-exact-count-enable = false
db-extra-search-path = "public,extensions,private"
db-max-rows = 1000
db-plan-enabled = true
Expand Down
1 change: 1 addition & 0 deletions test/io/configs/expected/no-defaults.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
db-anon-role = "root"
db-channel = "postgrest"
db-channel-enabled = false
db-exact-count-enable = false
db-extra-search-path = "public,test"
db-max-rows = 1000
db-plan-enabled = true
Expand Down
1 change: 1 addition & 0 deletions test/io/configs/expected/types.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
db-anon-role = ""
db-channel = "pgrst"
db-channel-enabled = true
db-exact-count-enable = true
db-extra-search-path = "public"
db-max-rows = ""
db-plan-enabled = false
Expand Down
1 change: 1 addition & 0 deletions test/io/configs/no-defaults-env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ PGRST_APP_SETTINGS_test: test
PGRST_DB_ANON_ROLE: root
PGRST_DB_CHANNEL: postgrest
PGRST_DB_CHANNEL_ENABLED: false
PGRST_DB_EXACT_COUNT_ENABLE: false
PGRST_DB_EXTRA_SEARCH_PATH: public, test
PGRST_DB_MAX_ROWS: 1000
PGRST_DB_PLAN_ENABLED: true
Expand Down
1 change: 1 addition & 0 deletions test/io/configs/no-defaults.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
db-anon-role = "root"
db-channel = "postgrest"
db-channel-enabled = false
db-exact-count-enable = false
db-extra-search-path = "public, test"
db-max-rows = 1000
db-plan-enabled = true
Expand Down
27 changes: 27 additions & 0 deletions test/spec/Feature/Query/DisableExactCountSpec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Feature.Query.DisableExactCountSpec where

import Network.Wai (Application)

import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai

import Protolude hiding (get)
import SpecHelper

spec :: SpecWith ((), Application)
spec =
describe "tests for config db-exact-count-enable" $ do

context "should not return exact count" $ do
it "returns * in content-range instead of exact count" $ do
request methodHead "/getallprojects_view"
[("Prefer", "count=exact")]
""
`shouldRespondWith`
""
{ matchStatus = 200
, matchHeaders = [ matchContentTypeJson
, "Content-Range" <:> "0-4/*"]
}

6 changes: 6 additions & 0 deletions test/spec/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import qualified Feature.OptionsSpec
import qualified Feature.Query.AndOrParamsSpec
import qualified Feature.Query.ComputedRelsSpec
import qualified Feature.Query.DeleteSpec
import qualified Feature.Query.DisableExactCountSpec
import qualified Feature.Query.EmbedDisambiguationSpec
import qualified Feature.Query.EmbedInnerJoinSpec
import qualified Feature.Query.ErrorSpec
Expand Down Expand Up @@ -112,6 +113,7 @@ main = do
pgSafeUpdateApp = app testPgSafeUpdateEnabledCfg
obsApp = app testObservabilityCfg
serverTiming = app testCfgServerTiming
exactCountEnable = app testCfgExactCountEnable

extraSearchPathApp = appDbs testCfgExtraSearchPath
unicodeApp = appDbs testUnicodeCfg
Expand Down Expand Up @@ -252,6 +254,10 @@ main = do
parallel $ before serverTiming $
describe "Feature.Query.ServerTimingSpec.spec" Feature.Query.ServerTimingSpec.spec

-- this test runs with db-exact-count-enable = false
before exactCountEnable $
describe "Feature.Query.DisableExactCountSpec" Feature.Query.DisableExactCountSpec.spec

-- Note: the rollback tests can not run in parallel, because they test persistance and
-- this results in race conditions

Expand Down
4 changes: 4 additions & 0 deletions test/spec/SpecHelper.hs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
, configDbAnonRole = Just "postgrest_test_anonymous"
, configDbChannel = mempty
, configDbChannelEnabled = True
, configDbExactCountEnable = True
, configDbExtraSearchPath = []
, configDbMaxRows = Nothing
, configDbPlanEnabled = False
Expand Down Expand Up @@ -236,6 +237,9 @@ testObservabilityCfg = baseCfg { configServerTraceHeader = Just $ mk "X-Request-
testCfgServerTiming :: AppConfig
testCfgServerTiming = baseCfg { configDbPlanEnabled = True }

testCfgExactCountEnable :: AppConfig
testCfgExactCountEnable = baseCfg { configDbExactCountEnable = False }

analyzeTable :: Text -> IO ()
analyzeTable tableName =
void $ readProcess "psql" ["-U", "postgres", "--set", "ON_ERROR_STOP=1", "-a", "-c", toS $ "ANALYZE test.\"" <> tableName <> "\""] []
Expand Down

0 comments on commit d7ad57e

Please sign in to comment.