feat: Allow limit=0 in query params to return an empty array

This commit is contained in:
Laurence Isla
2022-04-28 17:14:29 -05:00
committed by GitHub
parent d2aa50be52
commit 5ad8800773
4 changed files with 27 additions and 7 deletions
+1
View File
@@ -27,6 +27,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- #2176, Errors raised with `SQLSTATE` now include the message and the code in the response body - @laurenceisla - #2176, Errors raised with `SQLSTATE` now include the message and the code in the response body - @laurenceisla
- #2236, Support POSIX regular expression operators for row filtering - @enote-kane - #2236, Support POSIX regular expression operators for row filtering - @enote-kane
- #2202, Allow returning XML from RPCs - @fjf2002 - #2202, Allow returning XML from RPCs - @fjf2002
- #2269, Allow `limit=0` in the request query to return an empty array - @gautam1168, @laurenceisla
### Fixed ### Fixed
+11
View File
@@ -10,6 +10,8 @@ module PostgREST.RangeQuery (
, restrictRange , restrictRange
, rangeGeq , rangeGeq
, allRange , allRange
, limitZeroRange
, hasLimitZero
, NonnegRange , NonnegRange
, rangeStatusHeader , rangeStatusHeader
, contentRangeH , contentRangeH
@@ -74,6 +76,15 @@ rangeLeq :: Integer -> NonnegRange
rangeLeq n = rangeLeq n =
Range BoundaryBelowAll (BoundaryAbove n) Range BoundaryBelowAll (BoundaryAbove n)
-- Special case to allow limit 0 queries
-- https://github.com/PostgREST/postgrest/issues/1121
-- 0 <= x <= -1
limitZeroRange :: Range Integer
limitZeroRange = Range (BoundaryBelow 0) (BoundaryAbove (-1))
hasLimitZero :: Range Integer -> Bool
hasLimitZero r = rangeUpper r == rangeUpper limitZeroRange
rangeStatusHeader :: NonnegRange -> Int64 -> Maybe Int64 -> (Status, Header) rangeStatusHeader :: NonnegRange -> Int64 -> Maybe Int64 -> (Status, Header)
rangeStatusHeader topLevelRange queryTotal tableTotal = rangeStatusHeader topLevelRange queryTotal tableTotal =
let lower = rangeOffset topLevelRange let lower = rangeOffset topLevelRange
+10 -2
View File
@@ -50,6 +50,8 @@ import PostgREST.DbStructure.Identifiers (FieldName,
import PostgREST.DbStructure.Proc (ProcDescription (..), import PostgREST.DbStructure.Proc (ProcDescription (..),
ProcParam (..), ProcsMap) ProcParam (..), ProcsMap)
import PostgREST.RangeQuery (NonnegRange, allRange, import PostgREST.RangeQuery (NonnegRange, allRange,
hasLimitZero,
limitZeroRange,
rangeRequested) rangeRequested)
import PostgREST.Request.Preferences (PreferCount (..), import PostgREST.Request.Preferences (PreferCount (..),
PreferParameters (..), PreferParameters (..),
@@ -180,7 +182,7 @@ apiRequest :: AppConfig -> DbStructure -> Request -> RequestBody -> QueryParams.
apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..} apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..}
| isJust profile && fromJust profile `notElem` configDbSchemas = Left $ UnacceptableSchema $ toList configDbSchemas | isJust profile && fromJust profile `notElem` configDbSchemas = Left $ UnacceptableSchema $ toList configDbSchemas
| isTargetingProc && method `notElem` ["HEAD", "GET", "POST"] = Left ActionInappropriate | isTargetingProc && method `notElem` ["HEAD", "GET", "POST"] = Left ActionInappropriate
| topLevelRange == emptyRange = Left InvalidRange | isInvalidRange = Left InvalidRange
| shouldParsePayload && isLeft payload = either (Left . InvalidBody) witness payload | shouldParsePayload && isLeft payload = either (Left . InvalidBody) witness payload
| not expectParams && not (L.null qsParams) = Left $ ParseRequestError "Unexpected param or filter missing operator" ("Failed to parse " <> show qsParams) | not expectParams && not (L.null qsParams) = Left $ ParseRequestError "Unexpected param or filter missing operator" ("Failed to parse " <> show qsParams)
| otherwise = do | otherwise = do
@@ -330,8 +332,14 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
lookupHeader = flip lookup hdrs lookupHeader = flip lookup hdrs
Preferences.Preferences{..} = Preferences.fromHeaders hdrs Preferences.Preferences{..} = Preferences.fromHeaders hdrs
headerRange = rangeRequested hdrs headerRange = rangeRequested hdrs
limitRange = fromMaybe allRange (M.lookup "limit" qsRanges)
headerAndLimitRange = rangeIntersection headerRange limitRange
ranges = M.insert "limit" (rangeIntersection headerRange (fromMaybe allRange (M.lookup "limit" qsRanges))) qsRanges -- Bypass all the ranges and send only the limit zero range (0 <= x <= -1) if
-- limit=0 is present in the query params (not allowed for the Range header)
ranges = M.insert "limit" (if hasLimitZero limitRange then limitZeroRange else headerAndLimitRange) qsRanges
-- The only emptyRange allowed is the limit zero range
isInvalidRange = topLevelRange == emptyRange && not (hasLimitZero limitRange)
{-| {-|
Find the best match from a list of content types accepted by the Find the best match from a list of content types accepted by the
+4 -4
View File
@@ -184,11 +184,11 @@ spec = do
[json|[{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}]|] [json|[{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}]|]
{ matchHeaders = ["Content-Range" <:> "0-14/*"] } { matchHeaders = ["Content-Range" <:> "0-14/*"] }
it "fails if limit equals 0" $ it "succeeds and returns an empty array if limit equals 0" $
get "/items?select=id&limit=0" get "/items?select=id&limit=0"
`shouldRespondWith` [json|{"message":"HTTP Range error","code":"PGRST103","details":null,"hint":null}|] `shouldRespondWith` [json|[]|]
{ matchStatus = 416 { matchStatus = 200
, matchHeaders = [matchContentTypeJson] , matchHeaders = ["Content-Range" <:> "*/*"]
} }
it "fails if limit is negative" $ it "fails if limit is negative" $