feat: RPC POST for function w/single unnamed param
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)
This commit is contained in:
committed by
Steve Chavez
parent
caaa34b5de
commit
d4c6abbaec
BIN
Binary file not shown.
|
After Width: | Height: | Size: 324 B |
+77
-3
@@ -1,11 +1,12 @@
|
||||
module Feature.RpcSpec where
|
||||
|
||||
import qualified Data.ByteString.Lazy as BL (empty)
|
||||
import qualified Data.ByteString.Lazy as BL (empty, readFile)
|
||||
|
||||
import Network.Wai (Application)
|
||||
import Network.Wai.Test (SResponse (simpleBody, simpleHeaders, simpleStatus))
|
||||
|
||||
import Network.HTTP.Types
|
||||
import System.IO.Unsafe (unsafePerformIO)
|
||||
import Test.Hspec hiding (pendingWith)
|
||||
import Test.Hspec.Wai
|
||||
import Test.Hspec.Wai.JSON
|
||||
@@ -145,8 +146,8 @@ spec actualPgVersion =
|
||||
it "should fail with 300 Multiple Choices without explicit type casts" $
|
||||
get "/rpc/overloaded_same_args?arg=value" `shouldRespondWith`
|
||||
[json| {
|
||||
"hint":"Overloaded functions with the same parameter name but different types are not supported",
|
||||
"message":"Could not choose the best candidate function between: test.overloaded_same_args(arg => integer), test.overloaded_same_args(arg => xml), test.overloaded_same_args(arg => text, num => integer)" } |]
|
||||
"hint":"Try renaming the parameters or the function itself in the database so function overloading can be resolved",
|
||||
"message":"Could not choose the best candidate function between: test.overloaded_same_args(arg => integer), test.overloaded_same_args(arg => xml), test.overloaded_same_args(arg => text, num => integer)"}|]
|
||||
{ matchStatus = 300
|
||||
, matchHeaders = [matchContentTypeJson]
|
||||
}
|
||||
@@ -1059,3 +1060,76 @@ spec actualPgVersion =
|
||||
{ matchStatus = 500
|
||||
, matchHeaders = [ matchContentTypeJson ]
|
||||
}
|
||||
|
||||
context "single unnamed param" $ do
|
||||
it "can insert json directly" $
|
||||
post "/rpc/unnamed_json_param"
|
||||
[json|{"A": 1, "B": 2, "C": 3}|]
|
||||
`shouldRespondWith`
|
||||
[json|{"A": 1, "B": 2, "C": 3}|]
|
||||
|
||||
it "can insert text directly" $
|
||||
request methodPost "/rpc/unnamed_text_param"
|
||||
[("Content-Type", "text/plain"), ("Accept", "text/plain")]
|
||||
[str|unnamed text arg|]
|
||||
`shouldRespondWith`
|
||||
[str|unnamed text arg|]
|
||||
|
||||
it "can insert bytea directly" $ do
|
||||
let file = unsafePerformIO $ BL.readFile "test/C.png"
|
||||
r <- request methodPost "/rpc/unnamed_bytea_param"
|
||||
[("Content-Type", "application/octet-stream"), ("Accept", "application/octet-stream")]
|
||||
file
|
||||
liftIO $ do
|
||||
let respBody = simpleBody r
|
||||
respBody `shouldBe` file
|
||||
|
||||
it "will err when no function with single unnamed json parameter exists and application/json is specified" $
|
||||
request methodPost "/rpc/unnamed_int_param" [("Content-Type", "application/json")]
|
||||
[json|{"x": 1, "y": 2}|]
|
||||
`shouldRespondWith`
|
||||
[json|{
|
||||
"hint": "If a new function was created in the database with this name and parameters, try reloading the schema cache.",
|
||||
"message": "Could not find the test.unnamed_int_param(x, y) function or the test.unnamed_int_param function with a single unnamed json or jsonb parameter in the schema cache"
|
||||
}|]
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = [ matchContentTypeJson ]
|
||||
}
|
||||
|
||||
it "will err when no function with single unnamed text parameter exists and text/plain is specified" $
|
||||
request methodPost "/rpc/unnamed_int_param"
|
||||
[("Content-Type", "text/plain")]
|
||||
[str|a simple text|]
|
||||
`shouldRespondWith`
|
||||
[json|{
|
||||
"hint": "If a new function was created in the database with this name and parameters, try reloading the schema cache.",
|
||||
"message": "Could not find the test.unnamed_int_param function with a single unnamed text parameter in the schema cache"
|
||||
}|]
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = [ matchContentTypeJson ]
|
||||
}
|
||||
|
||||
it "will err when no function with single unnamed bytea parameter exists and application/octet-stream is specified" $
|
||||
let file = unsafePerformIO $ BL.readFile "test/C.png" in
|
||||
request methodPost "/rpc/unnamed_int_param"
|
||||
[("Content-Type", "application/octet-stream")]
|
||||
file
|
||||
`shouldRespondWith`
|
||||
[json|{
|
||||
"hint": "If a new function was created in the database with this name and parameters, try reloading the schema cache.",
|
||||
"message": "Could not find the test.unnamed_int_param function with a single unnamed bytea parameter in the schema cache"
|
||||
}|]
|
||||
{ matchStatus = 404
|
||||
, matchHeaders = [ matchContentTypeJson ]
|
||||
}
|
||||
|
||||
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 renaming the parameters or the function itself in the database 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]
|
||||
}
|
||||
|
||||
+12
-10
@@ -15,7 +15,7 @@ import Protolude hiding (get, toS)
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
import PostgREST.Query.QueryBuilder (requestToCallProcQuery)
|
||||
import PostgREST.Request.Types (CallQuery (..))
|
||||
import PostgREST.Request.Types
|
||||
|
||||
import PostgREST.DbStructure.Identifiers
|
||||
import PostgREST.DbStructure.Proc
|
||||
@@ -33,30 +33,32 @@ main = do
|
||||
context "call proc query" $ do
|
||||
it "should not exceed cost when calling setof composite proc" $ do
|
||||
cost <- exec pool $
|
||||
requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "get_projects_below") [ProcParam "id" "int" True False]
|
||||
(Just [str| {"id": 3} |]) False False False [])
|
||||
requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "get_projects_below")
|
||||
(KeyParams [ProcParam "id" "int" True False])
|
||||
(Just [str| {"id": 3} |]) False False [])
|
||||
liftIO $
|
||||
cost `shouldSatisfy` (< Just 40)
|
||||
|
||||
it "should not exceed cost when calling setof composite proc with empty params" $ do
|
||||
cost <- exec pool $
|
||||
requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "getallprojects") [] Nothing False False False [])
|
||||
requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "getallprojects") (KeyParams []) Nothing False False [])
|
||||
liftIO $
|
||||
cost `shouldSatisfy` (< Just 30)
|
||||
|
||||
it "should not exceed cost when calling scalar proc" $ do
|
||||
cost <- exec pool $
|
||||
requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "add_them")
|
||||
[ProcParam "a" "int" True False, ProcParam "b" "int" True False]
|
||||
(Just [str| {"a": 3, "b": 4} |]) True False False [])
|
||||
(KeyParams [ProcParam "a" "int" True False, ProcParam "b" "int" True False])
|
||||
(Just [str| {"a": 3, "b": 4} |]) True False [])
|
||||
liftIO $
|
||||
cost `shouldSatisfy` (< Just 10)
|
||||
|
||||
context "params=multiple-objects" $ do
|
||||
it "should not exceed cost when calling setof composite proc" $ do
|
||||
cost <- exec pool $
|
||||
requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "get_projects_below") [ProcParam "id" "int" True False]
|
||||
(Just [str| [{"id": 1}, {"id": 4}] |]) False True False [])
|
||||
requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "get_projects_below")
|
||||
(KeyParams [ProcParam "id" "int" True False])
|
||||
(Just [str| [{"id": 1}, {"id": 4}] |]) False True [])
|
||||
liftIO $ do
|
||||
-- lower bound needed for now to make sure that cost is not Nothing
|
||||
cost `shouldSatisfy` (> Just 2000)
|
||||
@@ -65,8 +67,8 @@ main = do
|
||||
it "should not exceed cost when calling scalar proc" $ do
|
||||
cost <- exec pool $
|
||||
requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "add_them")
|
||||
[ProcParam "a" "int" True False, ProcParam "b" "int" True False]
|
||||
(Just [str| [{"a": 3, "b": 4}, {"a": 1, "b": 2}, {"a": 8, "b": 7}] |]) True False False [])
|
||||
(KeyParams [ProcParam "a" "int" True False, ProcParam "b" "int" True False])
|
||||
(Just [str| [{"a": 3, "b": 4}, {"a": 1, "b": 2}, {"a": 8, "b": 7}] |]) True False [])
|
||||
liftIO $
|
||||
cost `shouldSatisfy` (< Just 10)
|
||||
|
||||
|
||||
Vendored
+26
-2
@@ -1050,7 +1050,7 @@ create function test.ret_point_overloaded(x int, y int) returns test.point_2d as
|
||||
select row(x, y)::test.point_2d;
|
||||
$$ language sql;
|
||||
|
||||
create function test.ret_point_overloaded(json) returns json as $$
|
||||
create function test.ret_point_overloaded(x json) returns json as $$
|
||||
select $1;
|
||||
$$ language sql;
|
||||
|
||||
@@ -2251,4 +2251,28 @@ A test for partitioned tables$$;
|
||||
foreign key (id_a, name_a) references test.partitioned_a (id, name)
|
||||
);
|
||||
end if;
|
||||
end$do$;
|
||||
end$do$;
|
||||
|
||||
create or replace function test.unnamed_json_param(json) returns json as $$
|
||||
select $1;
|
||||
$$ language sql;
|
||||
|
||||
create or replace function test.unnamed_text_param(text) returns text as $$
|
||||
select $1;
|
||||
$$ language sql ;
|
||||
|
||||
create or replace function test.unnamed_bytea_param(bytea) returns bytea as $$
|
||||
select $1::bytea;
|
||||
$$ language sql ;
|
||||
|
||||
create or replace function test.unnamed_int_param(int) returns int as $$
|
||||
select $1;
|
||||
$$ language sql;
|
||||
|
||||
create or replace function test.overloaded_unnamed_param(json) returns int as $$
|
||||
select $1;
|
||||
$$ language sql;
|
||||
|
||||
create or replace function test.overloaded_unnamed_param(x int, y int) returns int as $$
|
||||
select x + y;
|
||||
$$ language sql;
|
||||
|
||||
Reference in New Issue
Block a user