drop: Prefer: params=multiple-objects on RPC
BREAKING CHANGE A function with a JSON array or object parameter should be used instead
This commit is contained in:
committed by
Steve Chavez
parent
1442e02f5f
commit
bc1fb67df0
@@ -44,6 +44,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
+ Other methods should use the `limit/offset` query parameters for sub-ranges
|
+ Other methods should use the `limit/offset` query parameters for sub-ranges
|
||||||
+ `PUT` requests no longer return an error when this header is present (using `limit/offset` still triggers the error)
|
+ `PUT` requests no longer return an error when this header is present (using `limit/offset` still triggers the error)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- #2733, Remove bulk RPC call with the `Prefer: params=multiple-objects` header. A function with a JSON array or object parameter should be used instead.
|
||||||
|
|
||||||
## [10.1.2] - 2023-02-01
|
## [10.1.2] - 2023-02-01
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ fromHeaders headers =
|
|||||||
Preferences
|
Preferences
|
||||||
{ preferResolution = parsePrefs [MergeDuplicates, IgnoreDuplicates]
|
{ preferResolution = parsePrefs [MergeDuplicates, IgnoreDuplicates]
|
||||||
, preferRepresentation = fromMaybe None $ parsePrefs [Full, None, HeadersOnly]
|
, preferRepresentation = fromMaybe None $ parsePrefs [Full, None, HeadersOnly]
|
||||||
, preferParameters = parsePrefs [SingleObject, MultipleObjects]
|
, preferParameters = parsePrefs [SingleObject]
|
||||||
, preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount]
|
, preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount]
|
||||||
, preferTransaction = parsePrefs [Commit, Rollback]
|
, preferTransaction = parsePrefs [Commit, Rollback]
|
||||||
, preferMissing = parsePrefs [ApplyDefaults, ApplyNulls]
|
, preferMissing = parsePrefs [ApplyDefaults, ApplyNulls]
|
||||||
@@ -176,13 +176,10 @@ instance ToHeaderValue PreferRepresentation where
|
|||||||
-- | How to pass parameters to stored procedures.
|
-- | How to pass parameters to stored procedures.
|
||||||
data PreferParameters
|
data PreferParameters
|
||||||
= SingleObject -- ^ Pass all parameters as a single json object to a stored procedure.
|
= SingleObject -- ^ Pass all parameters as a single json object to a stored procedure.
|
||||||
| MultipleObjects -- ^ Pass an array of json objects as params to a stored procedure.
|
|
||||||
deriving Eq
|
deriving Eq
|
||||||
|
|
||||||
-- TODO: Deprecate params=multiple-objects in next major version
|
|
||||||
instance ToHeaderValue PreferParameters where
|
instance ToHeaderValue PreferParameters where
|
||||||
toHeaderValue SingleObject = "params=single-object"
|
toHeaderValue SingleObject = "params=single-object"
|
||||||
toHeaderValue MultipleObjects = "params=multiple-objects"
|
|
||||||
|
|
||||||
-- | How to determine the count of (expected) results
|
-- | How to determine the count of (expected) results
|
||||||
data PreferCount
|
data PreferCount
|
||||||
|
|||||||
@@ -548,7 +548,6 @@ callPlan proc ApiRequest{iPreferences=Preferences{..}} paramKeys args readReq =
|
|||||||
, funCParams = callParams
|
, funCParams = callParams
|
||||||
, funCArgs = Just args
|
, funCArgs = Just args
|
||||||
, funCScalar = procReturnsScalar proc
|
, funCScalar = procReturnsScalar proc
|
||||||
, funCMultipleCall = preferParameters == Just MultipleObjects
|
|
||||||
, funCReturning = inferColsEmbedNeeds readReq []
|
, funCReturning = inferColsEmbedNeeds readReq []
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ data CallPlan = FunctionCall
|
|||||||
, funCParams :: CallParams
|
, funCParams :: CallParams
|
||||||
, funCArgs :: Maybe LBS.ByteString
|
, funCArgs :: Maybe LBS.ByteString
|
||||||
, funCScalar :: Bool
|
, funCScalar :: Bool
|
||||||
, funCMultipleCall :: Bool
|
|
||||||
, funCReturning :: [FieldName]
|
, funCReturning :: [FieldName]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import Data.Scientific (FPFormat (..), formatScientific, isInteger)
|
|||||||
|
|
||||||
import PostgREST.ApiRequest (ApiRequest (..))
|
import PostgREST.ApiRequest (ApiRequest (..))
|
||||||
import PostgREST.ApiRequest.Preferences (PreferCount (..),
|
import PostgREST.ApiRequest.Preferences (PreferCount (..),
|
||||||
PreferParameters (..),
|
|
||||||
PreferTransaction (..),
|
PreferTransaction (..),
|
||||||
Preferences (..),
|
Preferences (..),
|
||||||
shouldCount)
|
shouldCount)
|
||||||
@@ -165,7 +164,6 @@ invokeQuery proc CallReadPlan{crReadPlan, crCallPlan, crBinField} apiReq@ApiRequ
|
|||||||
(QueryBuilder.readPlanToCountQuery crReadPlan)
|
(QueryBuilder.readPlanToCountQuery crReadPlan)
|
||||||
(shouldCount preferCount)
|
(shouldCount preferCount)
|
||||||
iAcceptMediaType
|
iAcceptMediaType
|
||||||
(preferParameters == Just MultipleObjects)
|
|
||||||
crBinField
|
crBinField
|
||||||
configDbPreparedStatements
|
configDbPreparedStatements
|
||||||
|
|
||||||
|
|||||||
@@ -164,14 +164,14 @@ mutatePlanToQuery (Delete mainQi logicForest range ordts returnings)
|
|||||||
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts)
|
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts)
|
||||||
|
|
||||||
callPlanToQuery :: CallPlan -> SQL.Snippet
|
callPlanToQuery :: CallPlan -> SQL.Snippet
|
||||||
callPlanToQuery (FunctionCall qi params args returnsScalar multipleCall returnings) =
|
callPlanToQuery (FunctionCall qi params args returnsScalar returnings) =
|
||||||
"SELECT " <> (if returnsScalar then "pgrst_call AS pgrst_scalar " else returnedColumns) <> " " <>
|
"SELECT " <> (if returnsScalar then "pgrst_call AS pgrst_scalar " else returnedColumns) <> " " <>
|
||||||
fromCall
|
fromCall
|
||||||
where
|
where
|
||||||
fromCall = case params of
|
fromCall = case params of
|
||||||
OnePosParam prm -> "FROM " <> callIt (singleParameter args $ encodeUtf8 $ ppType prm)
|
OnePosParam prm -> "FROM " <> callIt (singleParameter args $ encodeUtf8 $ ppType prm)
|
||||||
KeyParams [] -> "FROM " <> callIt mempty
|
KeyParams [] -> "FROM " <> callIt mempty
|
||||||
KeyParams prms -> fromJsonBodyF args ((\p -> TypedField (ppName p) (ppType p) Nothing) <$> prms) False (not multipleCall) False <> ", " <>
|
KeyParams prms -> fromJsonBodyF args ((\p -> TypedField (ppName p) (ppType p) Nothing) <$> prms) False True False <> ", " <>
|
||||||
"LATERAL " <> callIt (fmtParams prms)
|
"LATERAL " <> callIt (fmtParams prms)
|
||||||
|
|
||||||
callIt :: SQL.Snippet -> SQL.Snippet
|
callIt :: SQL.Snippet -> SQL.Snippet
|
||||||
|
|||||||
@@ -131,9 +131,9 @@ prepareRead selectQuery countQuery countTotal mt binaryField =
|
|||||||
_ -> HD.singleRow $ standardRow True
|
_ -> HD.singleRow $ standardRow True
|
||||||
|
|
||||||
prepareCall :: Bool -> Bool -> SQL.Snippet -> SQL.Snippet -> SQL.Snippet -> Bool ->
|
prepareCall :: Bool -> Bool -> SQL.Snippet -> SQL.Snippet -> SQL.Snippet -> Bool ->
|
||||||
MediaType -> Bool -> Maybe FieldName -> Bool ->
|
MediaType -> Maybe FieldName -> Bool ->
|
||||||
SQL.Statement () ResultSet
|
SQL.Statement () ResultSet
|
||||||
prepareCall returnsScalar returnsSingle callProcQuery selectQuery countQuery countTotal mt multObjects binaryField =
|
prepareCall returnsScalar returnsSingle callProcQuery selectQuery countQuery countTotal mt binaryField =
|
||||||
SQL.dynamicallyParameterized (mtSnippet mt snippet) decodeIt
|
SQL.dynamicallyParameterized (mtSnippet mt snippet) decodeIt
|
||||||
where
|
where
|
||||||
snippet =
|
snippet =
|
||||||
@@ -156,7 +156,7 @@ prepareCall returnsScalar returnsSingle callProcQuery selectQuery countQuery cou
|
|||||||
| getMediaType mt == MTGeoJSON = asGeoJsonF
|
| getMediaType mt == MTGeoJSON = asGeoJsonF
|
||||||
| isJust binaryField && getMediaType mt == MTTextXML = asXmlF $ fromJust binaryField
|
| isJust binaryField && getMediaType mt == MTTextXML = asXmlF $ fromJust binaryField
|
||||||
| isJust binaryField = asBinaryF $ fromJust binaryField
|
| isJust binaryField = asBinaryF $ fromJust binaryField
|
||||||
| returnsSingle && not multObjects = asJsonSingleF returnsScalar
|
| returnsSingle = asJsonSingleF returnsScalar
|
||||||
| otherwise = asJsonF returnsScalar
|
| otherwise = asJsonF returnsScalar
|
||||||
|
|
||||||
decodeIt :: HD.Result ResultSet
|
decodeIt :: HD.Result ResultSet
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import Network.HTTP.Types
|
|||||||
import Test.Hspec hiding (pendingWith)
|
import Test.Hspec hiding (pendingWith)
|
||||||
import Test.Hspec.Wai
|
import Test.Hspec.Wai
|
||||||
import Test.Hspec.Wai.JSON
|
import Test.Hspec.Wai.JSON
|
||||||
import Text.Heredoc
|
|
||||||
|
|
||||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion120,
|
import PostgREST.Config.PgVersion (PgVersion, pgVersion120,
|
||||||
pgVersion130)
|
pgVersion130)
|
||||||
@@ -331,21 +330,6 @@ spec actualPgVersion = do
|
|||||||
|
|
||||||
liftIO $ planCost r `shouldSatisfy` (< 1.18)
|
liftIO $ planCost r `shouldSatisfy` (< 1.18)
|
||||||
|
|
||||||
context "params=multiple-objects" $ do
|
|
||||||
it "should not exceed cost when calling setof composite proc" $ do
|
|
||||||
r <- request methodPost "/rpc/get_projects_below"
|
|
||||||
[planHdr, ("Prefer", "params=multiple-objects")]
|
|
||||||
[str| [{"id": 1}, {"id": 4}] |]
|
|
||||||
|
|
||||||
liftIO $ planCost r `shouldSatisfy` (< 4503.4)
|
|
||||||
|
|
||||||
it "should not exceed cost when calling scalar proc" $ do
|
|
||||||
r <- request methodPost "/rpc/add_them"
|
|
||||||
[planHdr, ("Prefer", "params=multiple-objects")]
|
|
||||||
[str| [{"a": 3, "b": 4}, {"a": 1, "b": 2}, {"a": 8, "b": 7}] |]
|
|
||||||
|
|
||||||
liftIO $ planCost r `shouldSatisfy` (< 5.85)
|
|
||||||
|
|
||||||
context "function inlining" $ do
|
context "function inlining" $ do
|
||||||
it "should inline a zero argument function(the function won't appear in the plan tree)" $ do
|
it "should inline a zero argument function(the function won't appear in the plan tree)" $ do
|
||||||
r <- request methodGet "/rpc/getallusers?id=eq.1"
|
r <- request methodGet "/rpc/getallusers?id=eq.1"
|
||||||
|
|||||||
@@ -930,42 +930,6 @@ spec actualPgVersion =
|
|||||||
`shouldRespondWith` "3"
|
`shouldRespondWith` "3"
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
context "bulk RPC with params=multiple-objects" $ do
|
|
||||||
it "works with a scalar function an returns a json array" $
|
|
||||||
request methodPost "/rpc/add_them" [("Prefer", "params=multiple-objects")]
|
|
||||||
[json|[
|
|
||||||
{"a": 1, "b": 2},
|
|
||||||
{"a": 4, "b": 6},
|
|
||||||
{"a": 100, "b": 200} ]|]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|
|
|
||||||
[3, 10, 300]
|
|
||||||
|] { matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
it "works with a scalar function an returns a json array when posting CSV" $
|
|
||||||
request methodPost "/rpc/add_them" [("Content-Type", "text/csv"), ("Prefer", "params=multiple-objects")]
|
|
||||||
"a,b\n1,2\n4,6\n100,200"
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|
|
|
||||||
[3, 10, 300]
|
|
||||||
|]
|
|
||||||
{ matchStatus = 200
|
|
||||||
, matchHeaders = [matchContentTypeJson]
|
|
||||||
}
|
|
||||||
|
|
||||||
it "works with a non-scalar result" $
|
|
||||||
request methodPost "/rpc/get_projects_below?select=id,name" [("Prefer", "params=multiple-objects")]
|
|
||||||
[json|[
|
|
||||||
{"id": 1},
|
|
||||||
{"id": 5} ]|]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|
|
|
||||||
[{"id":1,"name":"Windows 7"},
|
|
||||||
{"id":2,"name":"Windows 10"},
|
|
||||||
{"id":3,"name":"IOS"},
|
|
||||||
{"id":4,"name":"OSX"}]
|
|
||||||
|] { matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
context "HTTP request env vars" $ do
|
context "HTTP request env vars" $ do
|
||||||
it "custom header is set" $
|
it "custom header is set" $
|
||||||
request methodPost "/rpc/get_guc_value"
|
request methodPost "/rpc/get_guc_value"
|
||||||
|
|||||||
Reference in New Issue
Block a user