Add binary output to rpc, Fix #883

This commit is contained in:
steve-chavez
2017-06-24 22:45:53 -05:00
parent ecd58e1ad2
commit f0bd507ab9
5 changed files with 77 additions and 36 deletions
+1
View File
@@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added ### Added
- #889, Allow more than two conditions in a single and/or - @steve-chavez - #889, Allow more than two conditions in a single and/or - @steve-chavez
- #883, Binary output support for RPC - @steve-chavez
### Fixed ### Fixed
+16 -10
View File
@@ -232,19 +232,25 @@ app dbStructure conf apiRequest =
return $ responseLBS status200 [allOrigins, acceptH] "" return $ responseLBS status200 [allOrigins, acceptH] ""
(ActionInvoke, TargetProc qi, Just (PayloadJSON payload)) -> (ActionInvoke, TargetProc qi, Just (PayloadJSON payload)) ->
case readSqlParts of let proc = M.lookup (qiName qi) allProcs
Left errorResponse -> return errorResponse
Right (q, cq) -> do
let p = V.head payload
singular = contentType == CTSingularJSON
paramsAsSingleObject = iPreferSingleObjectParameter apiRequest
proc = M.lookup (qiName qi) allProcs
returnsScalar = case proc of returnsScalar = case proc of
Just ProcDescription{pdReturnType = (Single (Scalar _))} -> True Just ProcDescription{pdReturnType = (Single (Scalar _))} -> True
_ -> False _ -> False
rpcBinaryField = if returnsScalar
then Right Nothing
else binaryField contentType =<< fldNames
partsField = (,) <$> readSqlParts <*> rpcBinaryField in
case partsField of
Left errorResponse -> return errorResponse
Right ((q, cq), bField) -> do
let p = V.head payload
singular = contentType == CTSingularJSON
paramsAsSingleObject = iPreferSingleObjectParameter apiRequest
row <- H.query () $ row <- H.query () $
callProc qi p returnsScalar q cq topLevelRange shouldCount singular callProc qi p returnsScalar q cq topLevelRange shouldCount
paramsAsSingleObject (contentType == CTTextCSV) singular paramsAsSingleObject
(contentType == CTTextCSV)
(contentType == CTOctetStream) bField
let (tableTotal, queryTotal, body) = let (tableTotal, queryTotal, body) =
fromMaybe (Just 0, 0, "[]") row fromMaybe (Just 0, 0, "[]") row
(status, contentRange) = rangeHeader queryTotal tableTotal (status, contentRange) = rangeHeader queryTotal tableTotal
@@ -311,7 +317,7 @@ responseContentTypeOrError accepts action = serves contentTypesForRequest accept
ActionCreate -> [CTApplicationJSON, CTSingularJSON, CTTextCSV] ActionCreate -> [CTApplicationJSON, CTSingularJSON, CTTextCSV]
ActionUpdate -> [CTApplicationJSON, CTSingularJSON, CTTextCSV] ActionUpdate -> [CTApplicationJSON, CTSingularJSON, CTTextCSV]
ActionDelete -> [CTApplicationJSON, CTSingularJSON, CTTextCSV] ActionDelete -> [CTApplicationJSON, CTSingularJSON, CTTextCSV]
ActionInvoke -> [CTApplicationJSON, CTSingularJSON, CTTextCSV] ActionInvoke -> [CTApplicationJSON, CTSingularJSON, CTTextCSV, CTOctetStream]
ActionInspect -> [CTOpenAPI, CTApplicationJSON] ActionInspect -> [CTOpenAPI, CTApplicationJSON]
ActionInfo -> [CTTextCSV] ActionInfo -> [CTTextCSV]
serves sProduces cAccepts = serves sProduces cAccepts =
+9 -7
View File
@@ -145,8 +145,8 @@ createWriteStatement selectQuery mutateQuery wantSingle wantHdrs asCsv rep pKeys
type ProcResults = (Maybe Int64, Int64, ByteString) type ProcResults = (Maybe Int64, Int64, ByteString)
callProc :: QualifiedIdentifier -> JSON.Object -> Bool -> SqlQuery -> SqlQuery -> NonnegRange -> callProc :: QualifiedIdentifier -> JSON.Object -> Bool -> SqlQuery -> SqlQuery -> NonnegRange ->
Bool -> Bool -> Bool -> Bool -> H.Query () (Maybe ProcResults) Bool -> Bool -> Bool -> Bool -> Bool -> Maybe FieldName -> H.Query () (Maybe ProcResults)
callProc qi params returnsScalar selectQuery countQuery _ countTotal isSingle paramsAsJson asCsv = callProc qi params returnsScalar selectQuery countQuery _ countTotal isSingle paramsAsJson asCsv asBinary binaryField =
unicodeStatement sql HE.unit decodeProc True unicodeStatement sql HE.unit decodeProc True
where where
sql = sql =
@@ -155,7 +155,7 @@ callProc qi params returnsScalar selectQuery countQuery _ countTotal isSingle pa
SELECT SELECT
{countResultF} AS total_result_set, {countResultF} AS total_result_set,
1 AS page_total, 1 AS page_total,
(row_to_json(_postgrest_t)->{_procName})::character varying as body {scalarBodyF} as body
FROM ({selectQuery}) _postgrest_t;|] FROM ({selectQuery}) _postgrest_t;|]
else [qc| else [qc|
WITH {sourceCTEName} AS ({_callSql}) WITH {sourceCTEName} AS ({_callSql})
@@ -169,18 +169,20 @@ callProc qi params returnsScalar selectQuery countQuery _ countTotal isSingle pa
_args = if paramsAsJson _args = if paramsAsJson
then insertableValueWithType "json" $ JSON.Object params then insertableValueWithType "json" $ JSON.Object params
else intercalate "," $ map _assignment (HM.toList params) else intercalate "," $ map _assignment (HM.toList params)
_procName = pgFmtLit $ qiName qi _procName = qiName qi
_assignment (n,v) = pgFmtIdent n <> ":=" <> insertableValue v _assignment (n,v) = pgFmtIdent n <> ":=" <> insertableValue v
_callSql = [qc|select * from {fromQi qi}({_args}) |] :: Text _callSql = [qc|select * from {fromQi qi}({_args}) |] :: Text
_countExpr = if countTotal
then [qc|(select pg_catalog.count(*) from {sourceCTEName})|]
else "null::bigint" :: Text
decodeProc = HD.maybeRow procRow decodeProc = HD.maybeRow procRow
procRow = (,,) <$> HD.nullableValue HD.int8 <*> HD.value HD.int8 procRow = (,,) <$> HD.nullableValue HD.int8 <*> HD.value HD.int8
<*> HD.value HD.bytea <*> HD.value HD.bytea
scalarBodyF
| asBinary = asBinaryF _procName
| otherwise = "(row_to_json(_postgrest_t)->" <> pgFmtLit _procName <> ")::character varying"
bodyF bodyF
| isSingle = asJsonSingleF | isSingle = asJsonSingleF
| asCsv = asCsvF | asCsv = asCsvF
| isJust binaryField = asBinaryF $ fromJust binaryField
| otherwise = asJsonF | otherwise = asJsonF
pgFmtIdent :: SqlFragment -> SqlFragment pgFmtIdent :: SqlFragment -> SqlFragment
+23
View File
@@ -693,6 +693,7 @@ spec = do
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
describe "binary output" $ do describe "binary output" $ do
context "on GET" $ do
it "can query if a single column is selected" $ it "can query if a single column is selected" $
request methodGet "/images_base64?select=img&name=eq.A.png" (acceptHdrs "application/octet-stream") "" request methodGet "/images_base64?select=img&name=eq.A.png" (acceptHdrs "application/octet-stream") ""
`shouldRespondWith` "iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeAQMAAAAB/jzhAAAABlBMVEUAAAD/AAAb/40iAAAAP0lEQVQI12NgwAbYG2AE/wEYwQMiZB4ACQkQYZEAIgqAhAGIKLCAEQ8kgMT/P1CCEUwc4IMSzA3sUIIdCHECAGSQEkeOTUyCAAAAAElFTkSuQmCC" `shouldRespondWith` "iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeAQMAAAAB/jzhAAAABlBMVEUAAAD/AAAb/40iAAAAP0lEQVQI12NgwAbYG2AE/wEYwQMiZB4ACQkQYZEAIgqAhAGIKLCAEQ8kgMT/P1CCEUwc4IMSzA3sUIIdCHECAGSQEkeOTUyCAAAAAElFTkSuQmCC"
@@ -714,6 +715,28 @@ spec = do
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/octet-stream; charset=utf-8"] , matchHeaders = ["Content-Type" <:> "application/octet-stream; charset=utf-8"]
} }
context "on RPC" $ do
context "Proc that returns scalar" $
it "can query without selecting column" $
request methodPost "/rpc/ret_base64_bin" (acceptHdrs "application/octet-stream") ""
`shouldRespondWith` "iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeAQMAAAAB/jzhAAAABlBMVEUAAAD/AAAb/40iAAAAP0lEQVQI12NgwAbYG2AE/wEYwQMiZB4ACQkQYZEAIgqAhAGIKLCAEQ8kgMT/P1CCEUwc4IMSzA3sUIIdCHECAGSQEkeOTUyCAAAAAElFTkSuQmCC"
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/octet-stream; charset=utf-8"]
}
context "Proc that returns rows" $ do
it "can query if a single column is selected" $
request methodPost "/rpc/ret_rows_with_base64_bin?select=img" (acceptHdrs "application/octet-stream") ""
`shouldRespondWith` "iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeAQMAAAAB/jzhAAAABlBMVEUAAAD/AAAb/40iAAAAP0lEQVQI12NgwAbYG2AE/wEYwQMiZB4ACQkQYZEAIgqAhAGIKLCAEQ8kgMT/P1CCEUwc4IMSzA3sUIIdCHECAGSQEkeOTUyCAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAAB4AAAAeAQMAAAAB/jzhAAAABlBMVEX///8AAP94wDzzAAAAL0lEQVQIW2NgwAb+HwARH0DEDyDxwAZEyGAhLODqHmBRzAcn5GAS///A1IF14AAA5/Adbiiz/0gAAAAASUVORK5CYII="
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/octet-stream; charset=utf-8"]
}
it "fails if a single column is not selected" $
request methodPost "/rpc/ret_rows_with_base64_bin" (acceptHdrs "application/octet-stream") ""
`shouldRespondWith` 406
describe "HTTP request env vars" $ do describe "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"
+9
View File
@@ -1115,6 +1115,7 @@ create table images (
); );
create view images_base64 as ( create view images_base64 as (
-- encoding in base64 puts a '\n' after every 76 character due to legacy reasons, this is isn't necessary here so it's removed
select name, replace(encode(img, 'base64'), E'\n', '') as img from images select name, replace(encode(img, 'base64'), E'\n', '') as img from images
); );
@@ -1161,6 +1162,14 @@ $$ language sql;
create function test.ret_void() returns void as '' language sql; create function test.ret_void() returns void as '' language sql;
create function test.ret_base64_bin() returns text as $$
select i.img from test.images_base64 i where i.name = 'A.png';
$$ language sql;
create function test.ret_rows_with_base64_bin() returns setof test.images_base64 as $$
select i.name, i.img from test.images_base64 i;
$$ language sql;
create function test.single_article(id integer) returns test.articles as $$ create function test.single_article(id integer) returns test.articles as $$
select a.* from test.articles a where a.id = $1; select a.* from test.articles a where a.id = $1;
$$ language sql; $$ language sql;