Fix #1264, allow bulk RPC call
This commit is contained in:
committed by
Steve Chávez
parent
40b004c9f7
commit
bdfb11001e
@@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
- #690, Add `?columns` query parameter for faster bulk inserts, also ignores unspecified json keys in a payload - @steve-chavez
|
- #690, Add `?columns` query parameter for faster bulk inserts, also ignores unspecified json keys in a payload - @steve-chavez
|
||||||
- #1239, Add support for resource embedding on materialized views - @vitorbaptista
|
- #1239, Add support for resource embedding on materialized views - @vitorbaptista
|
||||||
|
- #1264, Add support for bulk RPC call - @steve-chavez
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -148,29 +148,18 @@ callProc :: QualifiedIdentifier -> [PgArg] -> Bool -> SqlQuery -> SqlQuery -> Bo
|
|||||||
callProc qi pgArgs returnsScalar selectQuery countQuery countTotal isSingle paramsAsSingleObject asCsv asBinary binaryField pgVer =
|
callProc qi pgArgs returnsScalar selectQuery countQuery countTotal isSingle paramsAsSingleObject asCsv asBinary binaryField pgVer =
|
||||||
unicodeStatement sql (HE.param HE.unknown) decodeProc True
|
unicodeStatement sql (HE.param HE.unknown) decodeProc True
|
||||||
where
|
where
|
||||||
sql =
|
sql =[qc|
|
||||||
if returnsScalar then [qc|
|
WITH
|
||||||
WITH {argsRecord},
|
{argsRecord},
|
||||||
{sourceCTEName} AS (
|
{sourceCTEName} AS (
|
||||||
SELECT {fromQi qi}({args})
|
{sourceBody}
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
{countResultF} AS total_result_set,
|
{countResultF} AS total_result_set,
|
||||||
1 AS page_total,
|
pg_catalog.count(_postgrest_t) AS page_total,
|
||||||
{scalarBodyF} AS body,
|
{bodyF} AS body,
|
||||||
{responseHeaders} AS response_headers
|
{responseHeaders} AS response_headers
|
||||||
FROM ({selectQuery}) _postgrest_t;|]
|
FROM ({selectQuery}) _postgrest_t;|]
|
||||||
else [qc|
|
|
||||||
WITH {argsRecord},
|
|
||||||
{sourceCTEName} AS (
|
|
||||||
SELECT * FROM {fromQi qi}({args})
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
{countResultF} AS total_result_set,
|
|
||||||
pg_catalog.count(_postgrest_t) AS page_total,
|
|
||||||
{bodyF} AS body,
|
|
||||||
{responseHeaders} AS response_headers
|
|
||||||
FROM ({selectQuery}) _postgrest_t;|]
|
|
||||||
|
|
||||||
(argsRecord, args)
|
(argsRecord, args)
|
||||||
| paramsAsSingleObject = ("_args_record AS (SELECT NULL)", "$1::json")
|
| paramsAsSingleObject = ("_args_record AS (SELECT NULL)", "$1::json")
|
||||||
@@ -182,25 +171,47 @@ callProc qi pgArgs returnsScalar selectQuery countQuery countTotal isSingle para
|
|||||||
"SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <>
|
"SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <>
|
||||||
intercalate ", " ((\a -> pgFmtIdent (pgaName a) <> " " <> pgaType a) <$> pgArgs) <> ")",
|
intercalate ", " ((\a -> pgFmtIdent (pgaName a) <> " " <> pgaType a) <$> pgArgs) <> ")",
|
||||||
")"]
|
")"]
|
||||||
, intercalate ", " ((\a -> pgFmtIdent (pgaName a) <> " := (SELECT " <> pgFmtIdent (pgaName a) <> " FROM _args_record)") <$> pgArgs))
|
, intercalate ", " ((\a -> pgFmtIdent (pgaName a) <> " := _args_record." <> pgFmtIdent (pgaName a)) <$> pgArgs))
|
||||||
|
|
||||||
|
sourceBody :: SqlFragment
|
||||||
|
sourceBody
|
||||||
|
| paramsAsSingleObject || null pgArgs =
|
||||||
|
if returnsScalar
|
||||||
|
then [qc| SELECT {fromQi qi}({args}) |]
|
||||||
|
else [qc| SELECT * FROM {fromQi qi}({args}) |]
|
||||||
|
| otherwise =
|
||||||
|
if returnsScalar
|
||||||
|
then [qc| SELECT {fromQi qi}({args}) FROM _args_record |]
|
||||||
|
else [qc| SELECT _.*
|
||||||
|
FROM _args_record,
|
||||||
|
LATERAL ( SELECT * FROM {fromQi qi}({args}) ) _ |]
|
||||||
|
|
||||||
|
bodyF
|
||||||
|
| returnsScalar = scalarBodyF
|
||||||
|
| isSingle = asJsonSingleF
|
||||||
|
| asCsv = asCsvF
|
||||||
|
| isJust binaryField = asBinaryF $ fromJust binaryField
|
||||||
|
| otherwise = asJsonF
|
||||||
|
|
||||||
|
scalarBodyF
|
||||||
|
| asBinary = asBinaryF _procName
|
||||||
|
| otherwise = unwords [
|
||||||
|
"CASE",
|
||||||
|
"WHEN pg_catalog.count(_postgrest_t) = 1",
|
||||||
|
"THEN (json_agg(_postgrest_t." <> pgFmtIdent _procName <> ")->0)::character varying",
|
||||||
|
"ELSE (json_agg(_postgrest_t." <> pgFmtIdent _procName <> "))::character varying",
|
||||||
|
"END"]
|
||||||
|
|
||||||
countResultF = if countTotal then "( "<> countQuery <> ")" else "null::bigint" :: Text
|
countResultF = if countTotal then "( "<> countQuery <> ")" else "null::bigint" :: Text
|
||||||
_procName = qiName qi
|
_procName = qiName qi
|
||||||
responseHeaders =
|
responseHeaders =
|
||||||
if pgVer >= pgVersion96
|
if pgVer >= pgVersion96
|
||||||
then "coalesce(nullif(current_setting('response.headers', true), ''), '[]')" :: Text -- nullif is used because of https://gist.github.com/steve-chavez/8d7033ea5655096903f3b52f8ed09a15
|
then "coalesce(nullif(current_setting('response.headers', true), ''), '[]')" :: Text -- nullif is used because of https://gist.github.com/steve-chavez/8d7033ea5655096903f3b52f8ed09a15
|
||||||
else "'[]'" :: Text
|
else "'[]'" :: Text
|
||||||
|
|
||||||
decodeProc = HD.rowMaybe procRow
|
decodeProc = HD.rowMaybe procRow
|
||||||
procRow = (,,,) <$> HD.nullableColumn HD.int8 <*> HD.column HD.int8
|
procRow = (,,,) <$> HD.nullableColumn HD.int8 <*> HD.column HD.int8
|
||||||
<*> HD.column HD.bytea <*> HD.column HD.bytea
|
<*> HD.column HD.bytea <*> HD.column HD.bytea
|
||||||
scalarBodyF
|
|
||||||
| asBinary = asBinaryF _procName
|
|
||||||
| otherwise = "(row_to_json(_postgrest_t)->" <> pgFmtLit _procName <> ")::character varying"
|
|
||||||
|
|
||||||
bodyF
|
|
||||||
| isSingle = asJsonSingleF
|
|
||||||
| asCsv = asCsvF
|
|
||||||
| isJust binaryField = asBinaryF $ fromJust binaryField
|
|
||||||
| otherwise = asJsonF
|
|
||||||
|
|
||||||
pgFmtIdent :: SqlFragment -> SqlFragment
|
pgFmtIdent :: SqlFragment -> SqlFragment
|
||||||
pgFmtIdent x = "\"" <> replace "\"" "\"\"" (trimNullChars $ toS x) <> "\""
|
pgFmtIdent x = "\"" <> replace "\"" "\"\"" (trimNullChars $ toS x) <> "\""
|
||||||
|
|||||||
@@ -349,6 +349,42 @@ spec =
|
|||||||
[json|"Hello, John"|]
|
[json|"Hello, John"|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
context "bulk RPC" $ do
|
||||||
|
it "works with a scalar function an returns a json array" $
|
||||||
|
post "/rpc/add_them"
|
||||||
|
[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")]
|
||||||
|
"a,b\n1,2\n4,6\n100,200"
|
||||||
|
`shouldRespondWith`
|
||||||
|
[json|
|
||||||
|
[3, 10, 300]
|
||||||
|
|]
|
||||||
|
{ matchStatus = 200
|
||||||
|
, matchHeaders = [matchContentTypeJson]
|
||||||
|
}
|
||||||
|
|
||||||
|
it "works with a non-scalar result" $
|
||||||
|
post "/rpc/get_projects_below?select=id,name"
|
||||||
|
[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 "only for GET rpc" $ do
|
context "only for GET rpc" $ do
|
||||||
it "should fail on mutating procs" $ do
|
it "should fail on mutating procs" $ do
|
||||||
get "/rpc/callcounter" `shouldRespondWith` 500
|
get "/rpc/callcounter" `shouldRespondWith` 500
|
||||||
|
|||||||
Vendored
+5
@@ -1660,3 +1660,8 @@ CREATE TABLE test.openapi_types(
|
|||||||
"a_real" real,
|
"a_real" real,
|
||||||
"a_double_precision" double precision
|
"a_double_precision" double precision
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create function add_them(a integer, b integer)
|
||||||
|
returns integer as $$
|
||||||
|
select a + b;
|
||||||
|
$$ language sql;
|
||||||
|
|||||||
Reference in New Issue
Block a user