-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: RPC POST for function w/single unnamed param #1927
feat: RPC POST for function w/single unnamed param #1927
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explanation of the breaking change
select $1; | ||
$$ language sql; | ||
|
||
create or replace function test.overloaded_unnamed_param(x int, y int) returns int as $$ | ||
select x + y; | ||
$$ language sql; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a func with an unnamed json parameter plus any number of overloaded pairs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Somehow it doesn't show where, but the above function is:
create or replace function test.overloaded_unnamed_param(json) returns int as $$
select $1;
$$ language sql;
test/Feature/RpcSpec.hs
Outdated
it "will not be able to resolve when a single unnamed json parameter exists and other overloaded functions exist" $ | ||
request methodPost "/rpc/overloaded_unnamed_param" [("Content-Type", "application/json")] | ||
[json|{"x": 1, "y": 2}|] | ||
`shouldRespondWith` | ||
[json| { | ||
"hint":"Try changing the parameters' names or the function name so function overloading can be resolved", | ||
"message":"Could not choose the best candidate function between: test.overloaded_unnamed_param( => json), test.overloaded_unnamed_param(x => integer, y => integer)"}|] | ||
{ matchStatus = 300 | ||
, matchHeaders = [matchContentTypeJson] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PostgREST won't be able to resolve the function called with POST because we match the function's parameters with the json keys and the unnamed parameter basically absorbs all the json keys into one parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I don't think there's a way to avoid this breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I don't think there's a way to avoid this breaking change.
It might be possible if a special route is added for the unnamed param, like /rpc/my_func/raw
, but that would be too ugly I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be possible if a special route is added for the unnamed param, like
/rpc/my_func/raw
, but that would be too ugly I think.
I agree on the ugliness. I think this should be left for another PR on supporting overloaded functions in general.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the following:
- get the keys from the json payload like we do
- if there is any function in the cache that matches the keys (i.e. has all keys as arguments and does not require any other arguments, that are not in the payload) -> call function via named arguments
- if no function is found, fallback to passing via unnamed json object
This would be non-breaking, but still support the same use-case we do right now: Without overloaded functions, it's the same. But additionally, it would allow to define kind of a "catch-all" function, that matches everything that is not defined specifically via a different overload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wolfgangwalther I think it's a nice approach to overcome the issue. I've opened the Draft PR #1996 to implement it, we could continue the idea there.
else if isInvPost && length params == 1 && (ppName <$> headMay params) == Just mempty | ||
then case headMay params of | ||
Just prm | contentType == CTApplicationJSON -> ppType prm `elem` ["json", "jsonb"] | ||
| contentType == CTTextPlain -> ppType prm == "text" | ||
| contentType == CTOctetStream -> ppType prm == "bytea" | ||
| otherwise -> False | ||
Nothing -> False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Match the function with the single unnamed parameter and content types.
It's necessary to only consider POST(isInvPost
) because otherwise we'd also break GET requests that also have overloaded functions with a single unnamed json param. contentType
isn't enough to determine we're in a POST because a GET can also send a Content-Type
header despite having no body. Also, we default to application/json
for all requests included GET.
If the POST condition is removed, some tests fail already.
src/PostgREST/Error.hs
Outdated
"hint" .= ("Try changing the parameters' names or the function name so function overloading can be resolved" :: Text), | ||
"message" .= ("Could not choose the best candidate function between: " <> T.intercalate ", " [pdSchema p <> "." <> pdName p <> "(" <> T.intercalate ", " [ppName a <> " => " <> ppType a | a <- pdParams p] <> ")" | p <- procs])] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"hint" .= ("Try changing the parameters' names or the function name so function overloading can be resolved" :: Text), | |
"message" .= ("Could not choose the best candidate function between: " <> T.intercalate ", " [pdSchema p <> "." <> pdName p <> "(" <> T.intercalate ", " [ppName a <> " => " <> ppType a | a <- pdParams p] <> ")" | p <- procs])] | |
"hint" .= ("Try renaming the parameters or the function itself in the database to resolve the function overloading issue" :: Text), | |
"details" .= ("Overloaded functions with the same argument name but different types are not supported"), | |
"message" .= ("Could not choose the best candidate function between: " <> T.intercalate ", " [pdSchema p <> "." <> pdName p <> "(" <> T.intercalate ", " [ppName a <> " => " <> ppType a | a <- pdParams p] <> ")" | p <- procs])] |
I think it should be specified that the change should be done in the database, otherwise, at first glance, it may look like the change should be done on the query.
Also, I think the old hint should be kept and moved to the "detail" field.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be specified that the change should be done in the database, otherwise, at first glance, it may look like the change should be done on the query
True, I agree.
Also, I think the old hint should be kept and moved to the "detail" field. WDYT?
The thing is that the "Overloaded functions with the same argument name but different types are not supported" error is no longer accurate now. This can be noted on the test I've added:
it "will not be able to resolve when a single unnamed json parameter exists and other overloaded functions exist" $
request methodPost "/rpc/overloaded_unnamed_param" [("Content-Type", "application/json")]
[json|{"x": 1, "y": 2}|]
`shouldRespondWith`
[json| {
"hint":"Try changing the parameters' names or the function name so function overloading can be resolved",
"message":"Could not choose the best candidate function between: test.overloaded_unnamed_param( => json), test.overloaded_unnamed_param(x => integer, y => integer)"}|]
{ matchStatus = 300
, matchHeaders = [matchContentTypeJson]
}
The above is not related to having the same parameter name with different types.
fef5837
to
bd8cb60
Compare
Clarify the difference between arguments and parameters. Parameters are part of the function definition, arguments are the values passed to the function. Also clarify the findProc function comments and error message.
Shortens requestToCallProcQuery, which should generate SQL in a more direct way.
For POST on RPC, allows: * passing a json object without using `Prefer: params=single-object` The function must be defined with a single unnamed json param and `Content-Type: application/json` must be specified. * uploading binary to a function The function must be defined with a single unnamed bytea param and `Content-Type: application/octet-stream` must be specified. * uploading raw text to a function The function must be defined with a single unnamed text param and `Content-Type: text/plain` must be specified. BREAKING CHANGE If there's a function "my_func" having a single unnamed json param and other overloaded pairs(with any number of params), PostgREST won't be able to resolve a POST request to "my_func". For solving this, you can name the unnamed json param. my_func(json) -> my_func(prm json)
bd8cb60
to
a7eab89
Compare
stack on FreeBSD is failing with a really weird error:
Which doesn't make sense because I'll merge now and revisit that one(if it keeps failing) later. |
Same failure happening on master. Must be something related to stack. I see Nix being supported on FreeBSD, wonder if we can switch to it there. |
Avoids the breaking change in #1927: If there's a function "my_func" having a single unnamed json param and other overloaded pairs(with any number of params), PostgREST won't be able to resolve a POST request to "my_func".
Closes #1735. For POST on RPC, allows:
Passing a json object without using
Prefer: params=single-object
. The function must be defined with a single unnamedjson
orjsonb
param andContent-Type: application/json
must be specified.Uploading binary to a function. The function must be defined with a single unnamed
bytea
param andContent-Type: application/octet-stream
must be specified.Uploading raw text to a function. The function must be defined with a single unnamed
text
param andContent-Type: text/plain
must be specified.BREAKING CHANGE for Overloaded Functions If there's a function
my_func
having a single unnamed json param and other overloaded pairs(with any number of params), PostgREST won't be able to resolve a POST request tomy_func
. For solving this, you can name the unnamed json param.