remove support for Prefer: params=single-object (#3757)
BREAKING CHANGE
Using this preference was deprecated in 6c3d7a9, in favor of Functions with an array of JSON objects.
This commit is contained in:
@@ -5,6 +5,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- #3757, Remove support for `Prefer: params=single-object` - @joelonsql
|
||||||
|
+ This preference was deprecated in favor of Functions with an array of JSON objects
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
|||||||
@@ -131,10 +131,6 @@ For this the ``Content-Type: application/json`` header must be included in the r
|
|||||||
|
|
||||||
If an overloaded function has a single ``json`` or ``jsonb`` unnamed parameter, PostgREST will call this function as a fallback provided that no other overloaded function is found with the parameters sent in the POST request.
|
If an overloaded function has a single ``json`` or ``jsonb`` unnamed parameter, PostgREST will call this function as a fallback provided that no other overloaded function is found with the parameters sent in the POST request.
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Sending the JSON request body as a single argument is also possible with :ref:`Prefer: params=single-object <prefer_params>` but this method is **deprecated**.
|
|
||||||
|
|
||||||
.. _function_single_unnamed:
|
.. _function_single_unnamed:
|
||||||
|
|
||||||
Functions with a single unnamed parameter
|
Functions with a single unnamed parameter
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ The following preferences are supported.
|
|||||||
- ``Prefer: missing``. See :ref:`bulk_insert_default`.
|
- ``Prefer: missing``. See :ref:`bulk_insert_default`.
|
||||||
- ``Prefer: max-affected``, See :ref:`prefer_max_affected`.
|
- ``Prefer: max-affected``, See :ref:`prefer_max_affected`.
|
||||||
- ``Prefer: tx``. See :ref:`prefer_tx`.
|
- ``Prefer: tx``. See :ref:`prefer_tx`.
|
||||||
- ``Prefer: params``. See :ref:`prefer_params`.
|
|
||||||
|
|
||||||
.. _prefer_handling:
|
.. _prefer_handling:
|
||||||
|
|
||||||
@@ -224,31 +223,3 @@ To illustrate the use of this preference, consider the following scenario where
|
|||||||
"details": "The query affects 14 rows",
|
"details": "The query affects 14 rows",
|
||||||
"hint": null
|
"hint": null
|
||||||
}
|
}
|
||||||
|
|
||||||
.. _prefer_params:
|
|
||||||
|
|
||||||
Single JSON object as Function Parameter
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Using this preference is **deprecated** in favor of :ref:`function_single_json`.
|
|
||||||
|
|
||||||
:code:`Prefer: params=single-object` allows sending the JSON request body as the single argument of a :ref:`function <functions>`.
|
|
||||||
|
|
||||||
.. code-block:: postgres
|
|
||||||
|
|
||||||
CREATE FUNCTION mult_them(param json) RETURNS int AS $$
|
|
||||||
SELECT (param->>'x')::int * (param->>'y')::int
|
|
||||||
$$ LANGUAGE SQL;
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
curl "http://localhost:3000/rpc/mult_them" \
|
|
||||||
-X POST -H "Content-Type: application/json" \
|
|
||||||
-H "Prefer: params=single-object" \
|
|
||||||
-d '{ "x": 4, "y": 2 }'
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
8
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ module PostgREST.ApiRequest.Preferences
|
|||||||
, PreferCount(..)
|
, PreferCount(..)
|
||||||
, PreferHandling(..)
|
, PreferHandling(..)
|
||||||
, PreferMissing(..)
|
, PreferMissing(..)
|
||||||
, PreferParameters(..)
|
|
||||||
, PreferRepresentation(..)
|
, PreferRepresentation(..)
|
||||||
, PreferResolution(..)
|
, PreferResolution(..)
|
||||||
, PreferTransaction(..)
|
, PreferTransaction(..)
|
||||||
@@ -37,7 +36,6 @@ import Protolude
|
|||||||
-- >>> import Text.Pretty.Simple (pPrint)
|
-- >>> import Text.Pretty.Simple (pPrint)
|
||||||
-- >>> deriving instance Show PreferResolution
|
-- >>> deriving instance Show PreferResolution
|
||||||
-- >>> deriving instance Show PreferRepresentation
|
-- >>> deriving instance Show PreferRepresentation
|
||||||
-- >>> deriving instance Show PreferParameters
|
|
||||||
-- >>> deriving instance Show PreferCount
|
-- >>> deriving instance Show PreferCount
|
||||||
-- >>> deriving instance Show PreferTransaction
|
-- >>> deriving instance Show PreferTransaction
|
||||||
-- >>> deriving instance Show PreferMissing
|
-- >>> deriving instance Show PreferMissing
|
||||||
@@ -51,7 +49,6 @@ data Preferences
|
|||||||
= Preferences
|
= Preferences
|
||||||
{ preferResolution :: Maybe PreferResolution
|
{ preferResolution :: Maybe PreferResolution
|
||||||
, preferRepresentation :: Maybe PreferRepresentation
|
, preferRepresentation :: Maybe PreferRepresentation
|
||||||
, preferParameters :: Maybe PreferParameters
|
|
||||||
, preferCount :: Maybe PreferCount
|
, preferCount :: Maybe PreferCount
|
||||||
, preferTransaction :: Maybe PreferTransaction
|
, preferTransaction :: Maybe PreferTransaction
|
||||||
, preferMissing :: Maybe PreferMissing
|
, preferMissing :: Maybe PreferMissing
|
||||||
@@ -71,7 +68,6 @@ data Preferences
|
|||||||
-- Preferences
|
-- Preferences
|
||||||
-- { preferResolution = Just IgnoreDuplicates
|
-- { preferResolution = Just IgnoreDuplicates
|
||||||
-- , preferRepresentation = Nothing
|
-- , preferRepresentation = Nothing
|
||||||
-- , preferParameters = Nothing
|
|
||||||
-- , preferCount = Just ExactCount
|
-- , preferCount = Just ExactCount
|
||||||
-- , preferTransaction = Nothing
|
-- , preferTransaction = Nothing
|
||||||
-- , preferMissing = Nothing
|
-- , preferMissing = Nothing
|
||||||
@@ -89,7 +85,6 @@ data Preferences
|
|||||||
-- Preferences
|
-- Preferences
|
||||||
-- { preferResolution = Just IgnoreDuplicates
|
-- { preferResolution = Just IgnoreDuplicates
|
||||||
-- , preferRepresentation = Nothing
|
-- , preferRepresentation = Nothing
|
||||||
-- , preferParameters = Nothing
|
|
||||||
-- , preferCount = Just ExactCount
|
-- , preferCount = Just ExactCount
|
||||||
-- , preferTransaction = Nothing
|
-- , preferTransaction = Nothing
|
||||||
-- , preferMissing = Just ApplyNulls
|
-- , preferMissing = Just ApplyNulls
|
||||||
@@ -122,7 +117,6 @@ data Preferences
|
|||||||
-- Preferences
|
-- Preferences
|
||||||
-- { preferResolution = Nothing
|
-- { preferResolution = Nothing
|
||||||
-- , preferRepresentation = Just Full
|
-- , preferRepresentation = Just Full
|
||||||
-- , preferParameters = Nothing
|
|
||||||
-- , preferCount = Just ExactCount
|
-- , preferCount = Just ExactCount
|
||||||
-- , preferTransaction = Just Commit
|
-- , preferTransaction = Just Commit
|
||||||
-- , preferMissing = Just ApplyDefaults
|
-- , preferMissing = Just ApplyDefaults
|
||||||
@@ -137,7 +131,6 @@ fromHeaders allowTxDbOverride acceptedTzNames headers =
|
|||||||
Preferences
|
Preferences
|
||||||
{ preferResolution = parsePrefs [MergeDuplicates, IgnoreDuplicates]
|
{ preferResolution = parsePrefs [MergeDuplicates, IgnoreDuplicates]
|
||||||
, preferRepresentation = parsePrefs [Full, None, HeadersOnly]
|
, preferRepresentation = parsePrefs [Full, None, HeadersOnly]
|
||||||
, preferParameters = parsePrefs [SingleObject]
|
|
||||||
, preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount]
|
, preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount]
|
||||||
, preferTransaction = if allowTxDbOverride then parsePrefs [Commit, Rollback] else Nothing
|
, preferTransaction = if allowTxDbOverride then parsePrefs [Commit, Rollback] else Nothing
|
||||||
, preferMissing = parsePrefs [ApplyDefaults, ApplyNulls]
|
, preferMissing = parsePrefs [ApplyDefaults, ApplyNulls]
|
||||||
@@ -151,7 +144,6 @@ fromHeaders allowTxDbOverride acceptedTzNames headers =
|
|||||||
mapToHeadVal = map toHeaderValue
|
mapToHeadVal = map toHeaderValue
|
||||||
acceptedPrefs = mapToHeadVal [MergeDuplicates, IgnoreDuplicates] ++
|
acceptedPrefs = mapToHeadVal [MergeDuplicates, IgnoreDuplicates] ++
|
||||||
mapToHeadVal [Full, None, HeadersOnly] ++
|
mapToHeadVal [Full, None, HeadersOnly] ++
|
||||||
mapToHeadVal [SingleObject] ++
|
|
||||||
mapToHeadVal [ExactCount, PlannedCount, EstimatedCount] ++
|
mapToHeadVal [ExactCount, PlannedCount, EstimatedCount] ++
|
||||||
mapToHeadVal [Commit, Rollback] ++
|
mapToHeadVal [Commit, Rollback] ++
|
||||||
mapToHeadVal [ApplyDefaults, ApplyNulls] ++
|
mapToHeadVal [ApplyDefaults, ApplyNulls] ++
|
||||||
@@ -179,7 +171,7 @@ fromHeaders allowTxDbOverride acceptedTzNames headers =
|
|||||||
prefMap = Map.fromList . fmap (\pref -> (toHeaderValue pref, pref))
|
prefMap = Map.fromList . fmap (\pref -> (toHeaderValue pref, pref))
|
||||||
|
|
||||||
prefAppliedHeader :: Preferences -> Maybe HTTP.Header
|
prefAppliedHeader :: Preferences -> Maybe HTTP.Header
|
||||||
prefAppliedHeader Preferences {preferResolution, preferRepresentation, preferParameters, preferCount, preferTransaction, preferMissing, preferHandling, preferTimezone, preferMaxAffected } =
|
prefAppliedHeader Preferences {preferResolution, preferRepresentation, preferCount, preferTransaction, preferMissing, preferHandling, preferTimezone, preferMaxAffected } =
|
||||||
if null prefsVals
|
if null prefsVals
|
||||||
then Nothing
|
then Nothing
|
||||||
else Just (HTTP.hPreferenceApplied, combined)
|
else Just (HTTP.hPreferenceApplied, combined)
|
||||||
@@ -189,7 +181,6 @@ prefAppliedHeader Preferences {preferResolution, preferRepresentation, preferPar
|
|||||||
toHeaderValue <$> preferResolution
|
toHeaderValue <$> preferResolution
|
||||||
, toHeaderValue <$> preferMissing
|
, toHeaderValue <$> preferMissing
|
||||||
, toHeaderValue <$> preferRepresentation
|
, toHeaderValue <$> preferRepresentation
|
||||||
, toHeaderValue <$> preferParameters
|
|
||||||
, toHeaderValue <$> preferCount
|
, toHeaderValue <$> preferCount
|
||||||
, toHeaderValue <$> preferTransaction
|
, toHeaderValue <$> preferTransaction
|
||||||
, toHeaderValue <$> preferHandling
|
, toHeaderValue <$> preferHandling
|
||||||
@@ -231,15 +222,6 @@ instance ToHeaderValue PreferRepresentation where
|
|||||||
toHeaderValue None = "return=minimal"
|
toHeaderValue None = "return=minimal"
|
||||||
toHeaderValue HeadersOnly = "return=headers-only"
|
toHeaderValue HeadersOnly = "return=headers-only"
|
||||||
|
|
||||||
-- | How to pass parameters to stored procedures.
|
|
||||||
-- TODO: deprecated. Remove on next major version.
|
|
||||||
data PreferParameters
|
|
||||||
= SingleObject -- ^ Pass all parameters as a single json object to a stored procedure.
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
instance ToHeaderValue PreferParameters where
|
|
||||||
toHeaderValue SingleObject = "params=single-object"
|
|
||||||
|
|
||||||
-- | How to determine the count of (expected) results
|
-- | How to determine the count of (expected) results
|
||||||
data PreferCount
|
data PreferCount
|
||||||
= ExactCount -- ^ Exact count (slower).
|
= ExactCount -- ^ Exact count (slower).
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ data ApiRequestError
|
|||||||
| LimitNoOrderError
|
| LimitNoOrderError
|
||||||
| NotFound
|
| NotFound
|
||||||
| NoRelBetween Text Text (Maybe Text) Text RelationshipsMap
|
| NoRelBetween Text Text (Maybe Text) Text RelationshipsMap
|
||||||
| NoRpc Text Text [Text] Bool MediaType Bool [QualifiedIdentifier] [Routine]
|
| NoRpc Text Text [Text] MediaType Bool [QualifiedIdentifier] [Routine]
|
||||||
| NotEmbedded Text
|
| NotEmbedded Text
|
||||||
| PutLimitNotAllowedError
|
| PutLimitNotAllowedError
|
||||||
| QueryParamError QPError
|
| QueryParamError QPError
|
||||||
|
|||||||
@@ -221,23 +221,22 @@ instance JSON.ToJSON ApiRequestError where
|
|||||||
(Just $ JSON.toJSONList (compressedRel <$> rels))
|
(Just $ JSON.toJSONList (compressedRel <$> rels))
|
||||||
(Just $ JSON.String $ "Try changing '" <> child <> "' to one of the following: " <> relHint rels <> ". Find the desired relationship in the 'details' key.")
|
(Just $ JSON.String $ "Try changing '" <> child <> "' to one of the following: " <> relHint rels <> ". Find the desired relationship in the 'details' key.")
|
||||||
|
|
||||||
toJSON (NoRpc schema procName argumentKeys hasPreferSingleObject contentType isInvPost allProcs overloadedProcs) =
|
toJSON (NoRpc schema procName argumentKeys contentType isInvPost allProcs overloadedProcs) =
|
||||||
let func = schema <> "." <> procName
|
let func = schema <> "." <> procName
|
||||||
prms = T.intercalate ", " argumentKeys
|
prms = T.intercalate ", " argumentKeys
|
||||||
prmsMsg = "(" <> prms <> ")"
|
prmsMsg = "(" <> prms <> ")"
|
||||||
prmsDet = " with parameter" <> (if length argumentKeys > 1 then "s " else " ") <> prms
|
prmsDet = " with parameter" <> (if length argumentKeys > 1 then "s " else " ") <> prms
|
||||||
fmtPrms p = if null argumentKeys then " without parameters" else p
|
fmtPrms p = if null argumentKeys then " without parameters" else p
|
||||||
onlySingleParams = hasPreferSingleObject || (isInvPost && contentType `elem` [MTTextPlain, MTTextXML, MTOctetStream])
|
onlySingleParams = isInvPost && contentType `elem` [MTTextPlain, MTTextXML, MTOctetStream]
|
||||||
in toJsonPgrstError
|
in toJsonPgrstError
|
||||||
SchemaCacheErrorCode02
|
SchemaCacheErrorCode02
|
||||||
("Could not find the function " <> func <> (if onlySingleParams then "" else fmtPrms prmsMsg) <> " in the schema cache")
|
("Could not find the function " <> func <> (if onlySingleParams then "" else fmtPrms prmsMsg) <> " in the schema cache")
|
||||||
(Just $ JSON.String $ "Searched for the function " <> func <>
|
(Just $ JSON.String $ "Searched for the function " <> func <>
|
||||||
(case (hasPreferSingleObject, isInvPost, contentType) of
|
(case (isInvPost, contentType) of
|
||||||
(True, _, _) -> " with a single json/jsonb parameter"
|
(True, MTTextPlain) -> " with a single unnamed text parameter"
|
||||||
(_, True, MTTextPlain) -> " with a single unnamed text parameter"
|
(True, MTTextXML) -> " with a single unnamed xml parameter"
|
||||||
(_, True, MTTextXML) -> " with a single unnamed xml parameter"
|
(True, MTOctetStream) -> " with a single unnamed bytea parameter"
|
||||||
(_, True, MTOctetStream) -> " with a single unnamed bytea parameter"
|
(True, MTApplicationJSON) -> fmtPrms prmsDet <> " or with a single unnamed json/jsonb parameter"
|
||||||
(_, True, MTApplicationJSON) -> fmtPrms prmsDet <> " or with a single unnamed json/jsonb parameter"
|
|
||||||
_ -> fmtPrms prmsDet) <>
|
_ -> fmtPrms prmsDet) <>
|
||||||
", but no matches were found in the schema cache.")
|
", but no matches were found in the schema cache.")
|
||||||
-- The hint will be null in the case of single unnamed parameter functions
|
-- The hint will be null in the case of single unnamed parameter functions
|
||||||
|
|||||||
+8
-14
@@ -166,12 +166,12 @@ mutateReadPlan mutation apiRequest@ApiRequest{iPreferences=Preferences{..},..}
|
|||||||
return $ MutateReadPlan rPlan mPlan SQL.Write handler mediaType mutation identifier
|
return $ MutateReadPlan rPlan mPlan SQL.Write handler mediaType mutation identifier
|
||||||
|
|
||||||
callReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> InvokeMethod -> Either Error CallReadPlan
|
callReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> InvokeMethod -> Either Error CallReadPlan
|
||||||
callReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferences{..},..} invMethod = do
|
callReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferences{preferHandling, invalidPrefs},..} invMethod = do
|
||||||
let paramKeys = case invMethod of
|
let paramKeys = case invMethod of
|
||||||
InvRead _ -> S.fromList $ fst <$> qsParams'
|
InvRead _ -> S.fromList $ fst <$> qsParams'
|
||||||
Inv -> iColumns
|
Inv -> iColumns
|
||||||
proc@Function{..} <- mapLeft ApiRequestError $
|
proc@Function{..} <- mapLeft ApiRequestError $
|
||||||
findProc identifier paramKeys (preferParameters == Just SingleObject) (dbRoutines sCache) iContentMediaType (invMethod == Inv)
|
findProc identifier paramKeys (dbRoutines sCache) iContentMediaType (invMethod == Inv)
|
||||||
let relIdentifier = QualifiedIdentifier pdSchema (fromMaybe pdName $ Routine.funcTableName proc) -- done so a set returning function can embed other relations
|
let relIdentifier = QualifiedIdentifier pdSchema (fromMaybe pdName $ Routine.funcTableName proc) -- done so a set returning function can embed other relations
|
||||||
rPlan <- readPlan relIdentifier conf sCache apiRequest
|
rPlan <- readPlan relIdentifier conf sCache apiRequest
|
||||||
let args = case (invMethod, iContentMediaType) of
|
let args = case (invMethod, iContentMediaType) of
|
||||||
@@ -207,10 +207,10 @@ inspectPlan apiRequest headersOnly schema = do
|
|||||||
Search a pg proc by matching name and arguments keys to parameters. Since a function can be overloaded,
|
Search a pg proc by matching name and arguments keys to parameters. Since a function can be overloaded,
|
||||||
the name is not enough to find it. An overloaded function can have a different volatility or even a different return type.
|
the name is not enough to find it. An overloaded function can have a different volatility or even a different return type.
|
||||||
-}
|
-}
|
||||||
findProc :: QualifiedIdentifier -> S.Set Text -> Bool -> RoutineMap -> MediaType -> Bool -> Either ApiRequestError Routine
|
findProc :: QualifiedIdentifier -> S.Set Text -> RoutineMap -> MediaType -> Bool -> Either ApiRequestError Routine
|
||||||
findProc qi argumentsKeys paramsAsSingleObject allProcs contentMediaType isInvPost =
|
findProc qi argumentsKeys allProcs contentMediaType isInvPost =
|
||||||
case matchProc of
|
case matchProc of
|
||||||
([], []) -> Left $ NoRpc (qiSchema qi) (qiName qi) (S.toList argumentsKeys) paramsAsSingleObject contentMediaType isInvPost (HM.keys allProcs) lookupProcName
|
([], []) -> Left $ NoRpc (qiSchema qi) (qiName qi) (S.toList argumentsKeys) contentMediaType isInvPost (HM.keys allProcs) lookupProcName
|
||||||
-- If there are no functions with named arguments, fallback to the single unnamed argument function
|
-- If there are no functions with named arguments, fallback to the single unnamed argument function
|
||||||
([], [proc]) -> Right proc
|
([], [proc]) -> Right proc
|
||||||
([], procs) -> Left $ AmbiguousRpc (toList procs)
|
([], procs) -> Left $ AmbiguousRpc (toList procs)
|
||||||
@@ -241,13 +241,9 @@ findProc qi argumentsKeys paramsAsSingleObject allProcs contentMediaType isInvPo
|
|||||||
matchesParams proc =
|
matchesParams proc =
|
||||||
let
|
let
|
||||||
params = pdParams proc
|
params = pdParams proc
|
||||||
firstType = (ppType <$> headMay params)
|
|
||||||
in
|
in
|
||||||
-- exceptional case for Prefer: params=single-object
|
|
||||||
if paramsAsSingleObject
|
|
||||||
then length params == 1 && (firstType == Just "json" || firstType == Just "jsonb")
|
|
||||||
-- If the function has no parameters, the arguments keys must be empty as well
|
-- If the function has no parameters, the arguments keys must be empty as well
|
||||||
else if null params
|
if null params
|
||||||
then null argumentsKeys && not (isInvPost && contentMediaType `elem` [MTOctetStream, MTTextPlain, MTTextXML])
|
then null argumentsKeys && not (isInvPost && contentMediaType `elem` [MTOctetStream, MTTextPlain, MTTextXML])
|
||||||
-- A function has optional and required parameters. Optional parameters have a default value and
|
-- A function has optional and required parameters. Optional parameters have a default value and
|
||||||
-- don't require arguments for the function to be executed, required parameters must have an argument present.
|
-- don't require arguments for the function to be executed, required parameters must have an argument present.
|
||||||
@@ -972,7 +968,7 @@ resolveOrError ctx (Just table) field =
|
|||||||
cf -> Right $ withJsonParse ctx cf
|
cf -> Right $ withJsonParse ctx cf
|
||||||
|
|
||||||
callPlan :: Routine -> ApiRequest -> S.Set FieldName -> CallArgs -> ReadPlanTree -> CallPlan
|
callPlan :: Routine -> ApiRequest -> S.Set FieldName -> CallArgs -> ReadPlanTree -> CallPlan
|
||||||
callPlan proc ApiRequest{iPreferences=Preferences{..}} paramKeys args readReq = FunctionCall {
|
callPlan proc ApiRequest{} paramKeys args readReq = FunctionCall {
|
||||||
funCQi = QualifiedIdentifier (pdSchema proc) (pdName proc)
|
funCQi = QualifiedIdentifier (pdSchema proc) (pdName proc)
|
||||||
, funCParams = callParams
|
, funCParams = callParams
|
||||||
, funCArgs = args
|
, funCArgs = args
|
||||||
@@ -982,11 +978,9 @@ callPlan proc ApiRequest{iPreferences=Preferences{..}} paramKeys args readReq =
|
|||||||
, funCReturning = inferColsEmbedNeeds readReq []
|
, funCReturning = inferColsEmbedNeeds readReq []
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
paramsAsSingleObject = preferParameters == Just SingleObject
|
|
||||||
specifiedParams = filter (\x -> ppName x `S.member` paramKeys)
|
specifiedParams = filter (\x -> ppName x `S.member` paramKeys)
|
||||||
callParams = case pdParams proc of
|
callParams = case pdParams proc of
|
||||||
[prm] | paramsAsSingleObject -> OnePosParam prm
|
[prm] | ppName prm == mempty -> OnePosParam prm
|
||||||
| ppName prm == mempty -> OnePosParam prm
|
|
||||||
| otherwise -> KeyParams $ specifiedParams [prm]
|
| otherwise -> KeyParams $ specifiedParams [prm]
|
||||||
prms -> KeyParams $ specifiedParams prms
|
prms -> KeyParams $ specifiedParams prms
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ actionResponse (DbCrudResult WrappedReadPlan{wrMedia, wrHdrsOnly=headersOnly, cr
|
|||||||
RSStandard{..} -> do
|
RSStandard{..} -> do
|
||||||
let
|
let
|
||||||
(status, contentRange) = RangeQuery.rangeStatusHeader iTopLevelRange rsQueryTotal rsTableTotal
|
(status, contentRange) = RangeQuery.rangeStatusHeader iTopLevelRange rsQueryTotal rsTableTotal
|
||||||
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing Nothing Nothing preferCount preferTransaction Nothing preferHandling preferTimezone Nothing []
|
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing Nothing preferCount preferTransaction Nothing preferHandling preferTimezone Nothing []
|
||||||
headers =
|
headers =
|
||||||
[ contentRange
|
[ contentRange
|
||||||
, ( "Content-Location"
|
, ( "Content-Location"
|
||||||
@@ -99,7 +99,7 @@ actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationCreate, mrMutateP
|
|||||||
pkCols = case mrMutatePlan of { Insert{insPkCols} -> insPkCols; _ -> mempty;}
|
pkCols = case mrMutatePlan of { Insert{insPkCols} -> insPkCols; _ -> mempty;}
|
||||||
prefHeader = prefAppliedHeader $
|
prefHeader = prefAppliedHeader $
|
||||||
Preferences (if null pkCols && isNothing (qsOnConflict iQueryParams) then Nothing else preferResolution)
|
Preferences (if null pkCols && isNothing (qsOnConflict iQueryParams) then Nothing else preferResolution)
|
||||||
preferRepresentation Nothing preferCount preferTransaction preferMissing preferHandling preferTimezone Nothing []
|
preferRepresentation preferCount preferTransaction preferMissing preferHandling preferTimezone Nothing []
|
||||||
headers =
|
headers =
|
||||||
catMaybes
|
catMaybes
|
||||||
[ if null rsLocation then
|
[ if null rsLocation then
|
||||||
@@ -139,7 +139,7 @@ actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationUpdate, mrMedia}
|
|||||||
contentRangeHeader =
|
contentRangeHeader =
|
||||||
Just . RangeQuery.contentRangeH 0 (rsQueryTotal - 1) $
|
Just . RangeQuery.contentRangeH 0 (rsQueryTotal - 1) $
|
||||||
if shouldCount preferCount then Just rsQueryTotal else Nothing
|
if shouldCount preferCount then Just rsQueryTotal else Nothing
|
||||||
prefHeader = prefAppliedHeader $ Preferences Nothing preferRepresentation Nothing preferCount preferTransaction preferMissing preferHandling preferTimezone preferMaxAffected []
|
prefHeader = prefAppliedHeader $ Preferences Nothing preferRepresentation preferCount preferTransaction preferMissing preferHandling preferTimezone preferMaxAffected []
|
||||||
headers = catMaybes [contentRangeHeader, prefHeader]
|
headers = catMaybes [contentRangeHeader, prefHeader]
|
||||||
|
|
||||||
let (status, headers', body) =
|
let (status, headers', body) =
|
||||||
@@ -158,7 +158,7 @@ actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationUpdate, mrMedia}
|
|||||||
actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationSingleUpsert, mrMedia} resultSet) ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} _ _ _ _ _ = case resultSet of
|
actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationSingleUpsert, mrMedia} resultSet) ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} _ _ _ _ _ = case resultSet of
|
||||||
RSStandard {..} -> do
|
RSStandard {..} -> do
|
||||||
let
|
let
|
||||||
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing preferRepresentation Nothing preferCount preferTransaction Nothing preferHandling preferTimezone Nothing []
|
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing preferRepresentation preferCount preferTransaction Nothing preferHandling preferTimezone Nothing []
|
||||||
cTHeader = contentTypeHeaders mrMedia ctxApiRequest
|
cTHeader = contentTypeHeaders mrMedia ctxApiRequest
|
||||||
|
|
||||||
let isInsertIfGTZero i = if i > 0 then HTTP.status201 else HTTP.status200
|
let isInsertIfGTZero i = if i > 0 then HTTP.status201 else HTTP.status200
|
||||||
@@ -181,7 +181,7 @@ actionResponse (DbCrudResult MutateReadPlan{mrMutation=MutationDelete, mrMedia}
|
|||||||
contentRangeHeader =
|
contentRangeHeader =
|
||||||
RangeQuery.contentRangeH 1 0 $
|
RangeQuery.contentRangeH 1 0 $
|
||||||
if shouldCount preferCount then Just rsQueryTotal else Nothing
|
if shouldCount preferCount then Just rsQueryTotal else Nothing
|
||||||
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing preferRepresentation Nothing preferCount preferTransaction Nothing preferHandling preferTimezone preferMaxAffected []
|
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing preferRepresentation preferCount preferTransaction Nothing preferHandling preferTimezone preferMaxAffected []
|
||||||
headers = contentRangeHeader : prefHeader
|
headers = contentRangeHeader : prefHeader
|
||||||
|
|
||||||
let (status, headers', body) =
|
let (status, headers', body) =
|
||||||
@@ -206,7 +206,7 @@ actionResponse (DbCallResult CallReadPlan{crMedia, crInvMthd=invMethod, crProc=p
|
|||||||
then Error.errorPayload $ Error.ApiRequestError $ ApiRequestTypes.InvalidRange
|
then Error.errorPayload $ Error.ApiRequestError $ ApiRequestTypes.InvalidRange
|
||||||
$ ApiRequestTypes.OutOfBounds (show $ RangeQuery.rangeOffset iTopLevelRange) (maybe "0" show rsTableTotal)
|
$ ApiRequestTypes.OutOfBounds (show $ RangeQuery.rangeOffset iTopLevelRange) (maybe "0" show rsTableTotal)
|
||||||
else LBS.fromStrict rsBody
|
else LBS.fromStrict rsBody
|
||||||
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing Nothing preferParameters preferCount preferTransaction Nothing preferHandling preferTimezone preferMaxAffected []
|
prefHeader = maybeToList . prefAppliedHeader $ Preferences Nothing Nothing preferCount preferTransaction Nothing preferHandling preferTimezone preferMaxAffected []
|
||||||
headers = contentRange : prefHeader
|
headers = contentRange : prefHeader
|
||||||
|
|
||||||
let (status', headers', body) =
|
let (status', headers', body) =
|
||||||
|
|||||||
@@ -175,7 +175,6 @@ makePreferParam ts =
|
|||||||
val :: Text -> [Text]
|
val :: Text -> [Text]
|
||||||
val = \case
|
val = \case
|
||||||
"count" -> ["count=none"]
|
"count" -> ["count=none"]
|
||||||
"params" -> ["params=single-object"]
|
|
||||||
"return" -> ["return=representation", "return=minimal", "return=none"]
|
"return" -> ["return=representation", "return=minimal", "return=none"]
|
||||||
"resolution" -> ["resolution=ignore-duplicates", "resolution=merge-duplicates"]
|
"resolution" -> ["resolution=ignore-duplicates", "resolution=merge-duplicates"]
|
||||||
_ -> []
|
_ -> []
|
||||||
|
|||||||
@@ -239,20 +239,6 @@ spec =
|
|||||||
, matchHeaders = [matchContentTypeJson]
|
, matchHeaders = [matchContentTypeJson]
|
||||||
}
|
}
|
||||||
|
|
||||||
it "should fail with 404 when no json arg is found with prefer single object" $
|
|
||||||
request methodPost "/rpc/sayhello"
|
|
||||||
[("Prefer","params=single-object")]
|
|
||||||
[json|{}|]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json| {
|
|
||||||
"hint":null,
|
|
||||||
"message":"Could not find the function test.sayhello in the schema cache",
|
|
||||||
"code":"PGRST202",
|
|
||||||
"details":"Searched for the function test.sayhello with a single json/jsonb parameter, but no matches were found in the schema cache."} |]
|
|
||||||
{ matchStatus = 404
|
|
||||||
, matchHeaders = [matchContentTypeJson]
|
|
||||||
}
|
|
||||||
|
|
||||||
it "should fail with 404 for overloaded functions with unknown args" $ do
|
it "should fail with 404 for overloaded functions with unknown args" $ do
|
||||||
get "/rpc/overloaded?wrong_arg=value" `shouldRespondWith`
|
get "/rpc/overloaded?wrong_arg=value" `shouldRespondWith`
|
||||||
[json| {
|
[json| {
|
||||||
@@ -515,13 +501,6 @@ spec =
|
|||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|{"x": 1, "y": 2}|]
|
[json|{"x": 1, "y": 2}|]
|
||||||
|
|
||||||
it "returns json scalar with prefer single object" $
|
|
||||||
request methodPost "/rpc/ret_point_overloaded" [("Prefer","params=single-object")]
|
|
||||||
[json|{"x": 1, "y": 2}|]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|{"x": 1, "y": 2}|]
|
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
context "proc argument types" $ do
|
context "proc argument types" $ do
|
||||||
it "accepts a variety of arguments (Postgres >= 10)" $
|
it "accepts a variety of arguments (Postgres >= 10)" $
|
||||||
post "/rpc/varied_arguments"
|
post "/rpc/varied_arguments"
|
||||||
@@ -793,40 +772,12 @@ spec =
|
|||||||
, matchHeaders = [ matchContentTypeJson ]
|
, matchHeaders = [ matchContentTypeJson ]
|
||||||
}
|
}
|
||||||
|
|
||||||
context "expects a single json object" $ do
|
|
||||||
it "does not expand posted json into parameters" $
|
|
||||||
request methodPost "/rpc/singlejsonparam"
|
|
||||||
[("prefer","params=single-object")] [json| { "p1": 1, "p2": "text", "p3" : {"obj":"text"} } |] `shouldRespondWith`
|
|
||||||
[json| { "p1": 1, "p2": "text", "p3" : {"obj":"text"} } |]
|
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
it "accepts parameters from an html form" $
|
|
||||||
request methodPost "/rpc/singlejsonparam"
|
|
||||||
[("Prefer","params=single-object"),("Content-Type", "application/x-www-form-urlencoded")]
|
|
||||||
("integer=7&double=2.71828&varchar=forms+are+fun&" <>
|
|
||||||
"boolean=false&date=1900-01-01&money=$3.99&enum=foo") `shouldRespondWith`
|
|
||||||
[json| { "integer": "7", "double": "2.71828", "varchar" : "forms are fun"
|
|
||||||
, "boolean":"false", "date":"1900-01-01", "money":"$3.99", "enum":"foo" } |]
|
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
it "works with GET" $
|
|
||||||
request methodGet "/rpc/singlejsonparam?p1=1&p2=text" [("Prefer","params=single-object")] ""
|
|
||||||
`shouldRespondWith` [json|{ "p1": "1", "p2": "text"}|]
|
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
context "should work with an overloaded function" $ do
|
context "should work with an overloaded function" $ do
|
||||||
it "overloaded()" $
|
it "overloaded()" $
|
||||||
get "/rpc/overloaded"
|
get "/rpc/overloaded"
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|[1,2,3]|]
|
[json|[1,2,3]|]
|
||||||
|
|
||||||
it "overloaded(json) single-object" $
|
|
||||||
request methodPost "/rpc/overloaded"
|
|
||||||
[("Prefer","params=single-object")]
|
|
||||||
[json|[{"x": 1, "y": "first"}, {"x": 2, "y": "second"}]|]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|[{"x": 1, "y": "first"}, {"x": 2, "y": "second"}]|]
|
|
||||||
|
|
||||||
it "overloaded(int, int)" $
|
it "overloaded(int, int)" $
|
||||||
get "/rpc/overloaded?a=1&b=2" `shouldRespondWith` [str|3|]
|
get "/rpc/overloaded?a=1&b=2" `shouldRespondWith` [str|3|]
|
||||||
|
|
||||||
@@ -840,13 +791,6 @@ spec =
|
|||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|[1,2,3]|]
|
[json|[1,2,3]|]
|
||||||
|
|
||||||
it "overloaded_html_form(json) single-object" $
|
|
||||||
request methodPost "/rpc/overloaded_html_form"
|
|
||||||
[("Content-Type", "application/x-www-form-urlencoded"), ("Prefer","params=single-object")]
|
|
||||||
"a=1&b=2&c=3"
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|{"a": "1", "b": "2", "c": "3"}|]
|
|
||||||
|
|
||||||
it "overloaded_html_form(int, int)" $
|
it "overloaded_html_form(int, int)" $
|
||||||
request methodPost "/rpc/overloaded_html_form"
|
request methodPost "/rpc/overloaded_html_form"
|
||||||
[("Content-Type", "application/x-www-form-urlencoded")]
|
[("Content-Type", "application/x-www-form-urlencoded")]
|
||||||
|
|||||||
Reference in New Issue
Block a user