Merge pull request #895 from steve-chavez/binary-proc
Binary output support for RPC
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
+18
-7
@@ -232,15 +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
|
||||||
|
returnsScalar = case proc of
|
||||||
|
Just ProcDescription{pdReturnType = (Single (Scalar _))} -> True
|
||||||
|
_ -> False
|
||||||
|
rpcBinaryField = if returnsScalar
|
||||||
|
then Right Nothing
|
||||||
|
else binaryField contentType =<< fldNames
|
||||||
|
partsField = (,) <$> readSqlParts <*> rpcBinaryField in
|
||||||
|
case partsField of
|
||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
Right (q, cq) -> do
|
Right ((q, cq), bField) -> do
|
||||||
let p = V.head payload
|
let p = V.head payload
|
||||||
singular = contentType == CTSingularJSON
|
singular = contentType == CTSingularJSON
|
||||||
paramsAsSingleObject = iPreferSingleObjectParameter apiRequest
|
paramsAsSingleObject = iPreferSingleObjectParameter apiRequest
|
||||||
row <- H.query () $
|
row <- H.query () $
|
||||||
callProc qi p 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
|
||||||
@@ -257,7 +267,7 @@ app dbStructure conf apiRequest =
|
|||||||
uri Nothing = ("http", host, port, "/")
|
uri Nothing = ("http", host, port, "/")
|
||||||
uri (Just Proxy { proxyScheme = s, proxyHost = h, proxyPort = p, proxyPath = b }) = (s, h, p, b)
|
uri (Just Proxy { proxyScheme = s, proxyHost = h, proxyPort = p, proxyPath = b }) = (s, h, p, b)
|
||||||
uri' = uri proxy
|
uri' = uri proxy
|
||||||
encodeApi ti = encodeOpenAPI (M.elems $ dbProcs dbStructure) ti uri'
|
encodeApi ti = encodeOpenAPI (M.elems allProcs) ti uri'
|
||||||
body <- encodeApi . toTableInfo <$> H.query schema accessibleTables
|
body <- encodeApi . toTableInfo <$> H.query schema accessibleTables
|
||||||
return $ responseLBS status200 [toHeader CTOpenAPI] $ toS body
|
return $ responseLBS status200 [toHeader CTOpenAPI] $ toS body
|
||||||
|
|
||||||
@@ -276,6 +286,7 @@ app dbStructure conf apiRequest =
|
|||||||
filterCol :: Schema -> TableName -> Column -> Bool
|
filterCol :: Schema -> TableName -> Column -> Bool
|
||||||
filterCol sc tb Column{colTable=Table{tableSchema=s, tableName=t}} = s==sc && t==tb
|
filterCol sc tb Column{colTable=Table{tableSchema=s, tableName=t}} = s==sc && t==tb
|
||||||
allPrKeys = dbPrimaryKeys dbStructure
|
allPrKeys = dbPrimaryKeys dbStructure
|
||||||
|
allProcs = dbProcs dbStructure
|
||||||
allOrigins = ("Access-Control-Allow-Origin", "*") :: Header
|
allOrigins = ("Access-Control-Allow-Origin", "*") :: Header
|
||||||
shouldCount = iPreferCount apiRequest
|
shouldCount = iPreferCount apiRequest
|
||||||
schema = toS $ configSchema conf
|
schema = toS $ configSchema conf
|
||||||
@@ -287,7 +298,7 @@ app dbStructure conf apiRequest =
|
|||||||
status = rangeStatus lower upper (toInteger <$> tableTotal)
|
status = rangeStatus lower upper (toInteger <$> tableTotal)
|
||||||
in (status, contentRange)
|
in (status, contentRange)
|
||||||
|
|
||||||
readReq = readRequest (configMaxRows conf) (dbRelations dbStructure) (dbProcs dbStructure) apiRequest
|
readReq = readRequest (configMaxRows conf) (dbRelations dbStructure) allProcs apiRequest
|
||||||
fldNames = fieldNames <$> readReq
|
fldNames = fieldNames <$> readReq
|
||||||
readDbRequest = DbRead <$> readReq
|
readDbRequest = DbRead <$> readReq
|
||||||
mutateDbRequest = DbMutate <$> (mutateRequest apiRequest =<< fldNames)
|
mutateDbRequest = DbMutate <$> (mutateRequest apiRequest =<< fldNames)
|
||||||
@@ -306,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 =
|
||||||
|
|||||||
@@ -137,7 +137,9 @@ accessibleProcs =
|
|||||||
qi = QualifiedIdentifier schema name
|
qi = QualifiedIdentifier schema name
|
||||||
pgType = case typ of
|
pgType = case typ of
|
||||||
'c' -> Composite qi
|
'c' -> Composite qi
|
||||||
'p' -> Pseudo name
|
'p' -> if name == "record" -- Only pg pseudo type that is a row type is 'record'
|
||||||
|
then Composite qi
|
||||||
|
else Scalar qi
|
||||||
_ -> Scalar qi -- 'b'ase, 'd'omain, 'e'num, 'r'ange
|
_ -> Scalar qi -- 'b'ase, 'd'omain, 'e'num, 'r'ange
|
||||||
|
|
||||||
parseVolatility :: Char -> ProcVolatility
|
parseVolatility :: Char -> ProcVolatility
|
||||||
|
|||||||
@@ -144,42 +144,45 @@ createWriteStatement selectQuery mutateQuery wantSingle wantHdrs asCsv rep pKeys
|
|||||||
| otherwise = asJsonF
|
| otherwise = asJsonF
|
||||||
|
|
||||||
type ProcResults = (Maybe Int64, Int64, ByteString)
|
type ProcResults = (Maybe Int64, Int64, ByteString)
|
||||||
callProc :: QualifiedIdentifier -> JSON.Object -> 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 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 = [qc|
|
sql =
|
||||||
WITH {sourceCTEName} AS ({_callSql})
|
if returnsScalar then [qc|
|
||||||
SELECT
|
WITH {sourceCTEName} AS ({_callSql})
|
||||||
{countResultF} AS total_result_set,
|
SELECT
|
||||||
pg_catalog.count(_postgrest_t) AS page_total,
|
{countResultF} AS total_result_set,
|
||||||
case
|
1 AS page_total,
|
||||||
when pg_catalog.count(*) > 1 then
|
{scalarBodyF} as body
|
||||||
{bodyF}
|
FROM ({selectQuery}) _postgrest_t;|]
|
||||||
else
|
else [qc|
|
||||||
coalesce(((array_agg(row_to_json(_postgrest_t)))[1]->{_procName})::character varying, {bodyF})
|
WITH {sourceCTEName} AS ({_callSql})
|
||||||
|
SELECT
|
||||||
|
{countResultF} AS total_result_set,
|
||||||
|
pg_catalog.count(_postgrest_t) AS page_total,
|
||||||
|
{bodyF} as body
|
||||||
|
FROM ({selectQuery}) _postgrest_t;|]
|
||||||
|
|
||||||
end as body
|
countResultF = if countTotal then "( "<> countQuery <> ")" else "null::bigint" :: Text
|
||||||
FROM ({selectQuery}) _postgrest_t;
|
|
||||||
|]
|
|
||||||
-- FROM (select * from {sourceCTEName} {limitF range}) t;
|
|
||||||
countResultF = if countTotal then "("<>countQuery<>")" else "null::bigint" :: Text
|
|
||||||
_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
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ data PgArg = PgArg {
|
|||||||
, pgaReq :: Bool
|
, pgaReq :: Bool
|
||||||
} deriving (Show, Eq)
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
data PgType = Scalar QualifiedIdentifier | Composite QualifiedIdentifier | Pseudo Text deriving (Eq, Show)
|
data PgType = Scalar QualifiedIdentifier | Composite QualifiedIdentifier deriving (Eq, Show)
|
||||||
|
|
||||||
data RetType = Single PgType | SetOf PgType deriving (Eq, Show)
|
data RetType = Single PgType | SetOf PgType deriving (Eq, Show)
|
||||||
|
|
||||||
|
|||||||
+58
-19
@@ -553,6 +553,18 @@ spec = do
|
|||||||
[json|"Hello, ¥"|]
|
[json|"Hello, ¥"|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
it "returns array" $
|
||||||
|
post "/rpc/ret_array" [json|{}|] `shouldRespondWith`
|
||||||
|
[json|[1, 2, 3]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
it "returns setof integers" $
|
||||||
|
post "/rpc/ret_setof_integers" [json|{}|] `shouldRespondWith`
|
||||||
|
[json|[{ "ret_setof_integers": 1 },
|
||||||
|
{ "ret_setof_integers": 2 },
|
||||||
|
{ "ret_setof_integers": 3 }]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "returns enum value" $
|
it "returns enum value" $
|
||||||
post "/rpc/ret_enum" [json|{ "val": "foo" }|] `shouldRespondWith`
|
post "/rpc/ret_enum" [json|{ "val": "foo" }|] `shouldRespondWith`
|
||||||
[json|"foo"|]
|
[json|"foo"|]
|
||||||
@@ -655,6 +667,10 @@ spec = do
|
|||||||
[json| "Return value of no parameters procedure." |]
|
[json| "Return value of no parameters procedure." |]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
it "returns proper output when having the same return col name as the proc name" $
|
||||||
|
post "/rpc/test" [json|{}|] `shouldRespondWith`
|
||||||
|
[json|[{"test":"hello","value":1}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
describe "weird requests" $ do
|
describe "weird requests" $ do
|
||||||
it "can query as normal" $ do
|
it "can query as normal" $ do
|
||||||
get "/Escap3e;" `shouldRespondWith`
|
get "/Escap3e;" `shouldRespondWith`
|
||||||
@@ -681,27 +697,50 @@ spec = do
|
|||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
describe "binary output" $ do
|
describe "binary output" $ do
|
||||||
it "can query if a single column is selected" $
|
context "on GET" $ do
|
||||||
request methodGet "/images_base64?select=img&name=eq.A.png" (acceptHdrs "application/octet-stream") ""
|
it "can query if a single column is selected" $
|
||||||
`shouldRespondWith` "iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeAQMAAAAB/jzhAAAABlBMVEUAAAD/AAAb/40iAAAAP0lEQVQI12NgwAbYG2AE/wEYwQMiZB4ACQkQYZEAIgqAhAGIKLCAEQ8kgMT/P1CCEUwc4IMSzA3sUIIdCHECAGSQEkeOTUyCAAAAAElFTkSuQmCC"
|
request methodGet "/images_base64?select=img&name=eq.A.png" (acceptHdrs "application/octet-stream") ""
|
||||||
{ matchStatus = 200
|
`shouldRespondWith` "iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeAQMAAAAB/jzhAAAABlBMVEUAAAD/AAAb/40iAAAAP0lEQVQI12NgwAbYG2AE/wEYwQMiZB4ACQkQYZEAIgqAhAGIKLCAEQ8kgMT/P1CCEUwc4IMSzA3sUIIdCHECAGSQEkeOTUyCAAAAAElFTkSuQmCC"
|
||||||
, matchHeaders = ["Content-Type" <:> "application/octet-stream; charset=utf-8"]
|
{ matchStatus = 200
|
||||||
}
|
, matchHeaders = ["Content-Type" <:> "application/octet-stream; charset=utf-8"]
|
||||||
|
}
|
||||||
|
|
||||||
it "fails if a single column is not selected" $ do
|
it "fails if a single column is not selected" $ do
|
||||||
request methodGet "/images?select=img,name&name=eq.A.png" (acceptHdrs "application/octet-stream") ""
|
request methodGet "/images?select=img,name&name=eq.A.png" (acceptHdrs "application/octet-stream") ""
|
||||||
`shouldRespondWith` 406
|
`shouldRespondWith` 406
|
||||||
request methodGet "/images?select=*&name=eq.A.png" (acceptHdrs "application/octet-stream") ""
|
request methodGet "/images?select=*&name=eq.A.png" (acceptHdrs "application/octet-stream") ""
|
||||||
`shouldRespondWith` 406
|
`shouldRespondWith` 406
|
||||||
request methodGet "/images?name=eq.A.png" (acceptHdrs "application/octet-stream") ""
|
request methodGet "/images?name=eq.A.png" (acceptHdrs "application/octet-stream") ""
|
||||||
`shouldRespondWith` 406
|
`shouldRespondWith` 406
|
||||||
|
|
||||||
|
it "concatenates results if more than one row is returned" $
|
||||||
|
request methodGet "/images_base64?select=img&name=in.A.png,B.png" (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"]
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
it "concatenates results if more than one row is returned" $
|
|
||||||
request methodGet "/images_base64?select=img&name=in.A.png,B.png" (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"]
|
|
||||||
}
|
|
||||||
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"
|
||||||
|
|||||||
Vendored
+23
@@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1124,6 +1125,10 @@ $$ language sql;
|
|||||||
|
|
||||||
create domain one_nine as integer check (value >= 1 and value <= 9);
|
create domain one_nine as integer check (value >= 1 and value <= 9);
|
||||||
|
|
||||||
|
create function test.ret_array() returns integer[] as $$
|
||||||
|
select '{1,2,3}'::integer[];
|
||||||
|
$$ language sql;
|
||||||
|
|
||||||
create function test.ret_domain(val integer) returns test.one_nine as $$
|
create function test.ret_domain(val integer) returns test.one_nine as $$
|
||||||
select val::test.one_nine;
|
select val::test.one_nine;
|
||||||
$$ language sql;
|
$$ language sql;
|
||||||
@@ -1132,6 +1137,10 @@ create function test.ret_range(low integer, up integer) returns int4range as $$
|
|||||||
select int4range(low, up);
|
select int4range(low, up);
|
||||||
$$ language sql;
|
$$ language sql;
|
||||||
|
|
||||||
|
create function test.ret_setof_integers() returns setof integer as $$
|
||||||
|
values (1), (2), (3);
|
||||||
|
$$ language sql;
|
||||||
|
|
||||||
create function test.ret_scalars() returns table(
|
create function test.ret_scalars() returns table(
|
||||||
a text, b test.enum_menagerie_type, c test.one_nine, d int4range
|
a text, b test.enum_menagerie_type, c test.one_nine, d int4range
|
||||||
) as $$
|
) as $$
|
||||||
@@ -1153,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;
|
||||||
@@ -1198,6 +1215,12 @@ create table grandchild_entities (
|
|||||||
jsonb_col jsonb
|
jsonb_col jsonb
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Used for testing that having the same return column name as the proc name
|
||||||
|
-- doesn't conflict with the required output, details in #901
|
||||||
|
create function test.test() returns table(test text, value int) as $$
|
||||||
|
values ('hello', 1);
|
||||||
|
$$ language sql;
|
||||||
|
|
||||||
--
|
--
|
||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|||||||
Reference in New Issue
Block a user