Fix RPC return type handling for domains with composite base type
This commit is contained in:
committed by
Steve Chavez
parent
3f690ec78f
commit
ca7ffd0a70
@@ -24,6 +24,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- #1162, Fix location header for POST request with select= without PK - @wolfgangwalther
|
- #1162, Fix location header for POST request with select= without PK - @wolfgangwalther
|
||||||
- #1585, Fix error messages on connection failure for localized postgres on Windows - @wolfgangwalther
|
- #1585, Fix error messages on connection failure for localized postgres on Windows - @wolfgangwalther
|
||||||
- #1636, Fix `application/octet-stream` appending `charset=utf-8` - @steve-chavez
|
- #1636, Fix `application/octet-stream` appending `charset=utf-8` - @steve-chavez
|
||||||
|
- #1615, Fix RPC return type handling and embedding for domains with composite base type - @wolfgangwalther
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ decodeProcs =
|
|||||||
<$> column HD.text
|
<$> column HD.text
|
||||||
<*> column HD.text
|
<*> column HD.text
|
||||||
<*> column HD.bool
|
<*> column HD.bool
|
||||||
<*> column HD.char)
|
<*> column HD.bool)
|
||||||
<*> (parseVolatility <$> column HD.char)
|
<*> (parseVolatility <$> column HD.char)
|
||||||
<*> pure False
|
<*> pure False
|
||||||
|
|
||||||
@@ -165,18 +165,15 @@ decodeProcs =
|
|||||||
else Just $
|
else Just $
|
||||||
PgArg (dropAround (== '"') name) (strip typ) (T.null def) isVariadic
|
PgArg (dropAround (== '"') name) (strip typ) (T.null def) isVariadic
|
||||||
|
|
||||||
parseRetType :: Text -> Text -> Bool -> Char -> RetType
|
parseRetType :: Text -> Text -> Bool -> Bool -> RetType
|
||||||
parseRetType schema name isSetOf typ
|
parseRetType schema name isSetOf isComposite
|
||||||
| isSetOf = SetOf pgType
|
| isSetOf = SetOf pgType
|
||||||
| otherwise = Single pgType
|
| otherwise = Single pgType
|
||||||
where
|
where
|
||||||
qi = QualifiedIdentifier schema name
|
qi = QualifiedIdentifier schema name
|
||||||
pgType = case typ of
|
pgType
|
||||||
'c' -> Composite qi
|
| isComposite = Composite qi
|
||||||
'p' -> if name == "record" -- Only pg pseudo type that is a row type is 'record'
|
| otherwise = Scalar qi
|
||||||
then Composite qi
|
|
||||||
else Scalar qi
|
|
||||||
_ -> Scalar qi -- 'b'ase, 'd'omain, 'e'num, 'r'ange
|
|
||||||
|
|
||||||
parseVolatility :: Char -> ProcVolatility
|
parseVolatility :: Char -> ProcVolatility
|
||||||
parseVolatility v | v == 'i' = Immutable
|
parseVolatility v | v == 'i' = Immutable
|
||||||
@@ -195,22 +192,47 @@ accessibleProcs = H.Statement (toS sql) (param HE.text) decodeProcs True
|
|||||||
|
|
||||||
procsSqlQuery :: SqlQuery
|
procsSqlQuery :: SqlQuery
|
||||||
procsSqlQuery = [q|
|
procsSqlQuery = [q|
|
||||||
|
-- Recursively get the base types of domains
|
||||||
|
WITH RECURSIVE
|
||||||
|
rec_types AS (
|
||||||
|
SELECT
|
||||||
|
oid,
|
||||||
|
typbasetype,
|
||||||
|
COALESCE(NULLIF(typbasetype, 0), oid) AS base
|
||||||
|
FROM pg_type
|
||||||
|
UNION
|
||||||
|
SELECT
|
||||||
|
t.oid,
|
||||||
|
b.typbasetype,
|
||||||
|
COALESCE(NULLIF(b.typbasetype, 0), b.oid) AS base
|
||||||
|
FROM rec_types t
|
||||||
|
JOIN pg_type b ON t.typbasetype = b.oid
|
||||||
|
),
|
||||||
|
base_types AS (
|
||||||
|
SELECT
|
||||||
|
oid,
|
||||||
|
base
|
||||||
|
FROM rec_types
|
||||||
|
WHERE typbasetype = 0
|
||||||
|
)
|
||||||
SELECT
|
SELECT
|
||||||
pn.nspname as proc_schema,
|
pn.nspname AS proc_schema,
|
||||||
p.proname as proc_name,
|
p.proname AS proc_name,
|
||||||
d.description as proc_description,
|
d.description AS proc_description,
|
||||||
pg_get_function_arguments(p.oid) as args,
|
pg_get_function_arguments(p.oid) AS args,
|
||||||
tn.nspname as rettype_schema,
|
tn.nspname AS schema,
|
||||||
coalesce(comp.relname, t.typname) as rettype_name,
|
COALESCE(comp.relname, t.typname) AS name,
|
||||||
p.proretset as rettype_is_setof,
|
p.proretset AS rettype_is_setof,
|
||||||
t.typtype as rettype_typ,
|
-- Only pg pseudo type that is a row type is 'record'
|
||||||
|
(t.typtype = 'c' or t.typtype = 'p' and t.typname = 'record') AS rettype_is_composite,
|
||||||
p.provolatile
|
p.provolatile
|
||||||
FROM pg_proc p
|
FROM pg_proc p
|
||||||
JOIN pg_namespace pn ON pn.oid = p.pronamespace
|
JOIN pg_namespace pn ON pn.oid = p.pronamespace
|
||||||
JOIN pg_type t ON t.oid = p.prorettype
|
JOIN base_types bt ON bt.oid = p.prorettype
|
||||||
JOIN pg_namespace tn ON tn.oid = t.typnamespace
|
JOIN pg_type t ON t.oid = bt.base
|
||||||
LEFT JOIN pg_class comp ON comp.oid = t.typrelid
|
JOIN pg_namespace tn ON tn.oid = t.typnamespace
|
||||||
LEFT JOIN pg_catalog.pg_description as d on d.objoid = p.oid
|
LEFT JOIN pg_class comp ON comp.oid = t.typrelid
|
||||||
|
LEFT JOIN pg_catalog.pg_description as d ON d.objoid = p.oid
|
||||||
|]
|
|]
|
||||||
|
|
||||||
schemaDescription :: H.Statement Schema (Maybe Text)
|
schemaDescription :: H.Statement Schema (Maybe Text)
|
||||||
|
|||||||
@@ -197,6 +197,16 @@ spec actualPgVersion =
|
|||||||
]|]
|
]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
when (actualPgVersion >= pgVersion110) $
|
||||||
|
it "can embed if rpc returns domain of table type" $ do
|
||||||
|
post "/rpc/getproject_domain?select=id,name,client:clients(id),tasks(id)"
|
||||||
|
[json| { "id": 1} |]
|
||||||
|
`shouldRespondWith`
|
||||||
|
[json|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|]
|
||||||
|
get "/rpc/getproject_domain?id=1&select=id,name,client:clients(id),tasks(id)"
|
||||||
|
`shouldRespondWith`
|
||||||
|
[json|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|]
|
||||||
|
|
||||||
context "a proc that returns an empty rowset" $
|
context "a proc that returns an empty rowset" $
|
||||||
it "returns empty json array" $ do
|
it "returns empty json array" $ do
|
||||||
post "/rpc/test_empty_rowset" [json| {} |] `shouldRespondWith`
|
post "/rpc/test_empty_rowset" [json| {} |] `shouldRespondWith`
|
||||||
@@ -258,6 +268,12 @@ spec actualPgVersion =
|
|||||||
it "cannot return composite type in hidden schema" $
|
it "cannot return composite type in hidden schema" $
|
||||||
post "/rpc/ret_point_3d" [json|{}|] `shouldRespondWith` 401
|
post "/rpc/ret_point_3d" [json|{}|] `shouldRespondWith` 401
|
||||||
|
|
||||||
|
when (actualPgVersion >= pgVersion110) $
|
||||||
|
it "returns domain of composite type" $
|
||||||
|
post "/rpc/ret_composite_domain" [json|{}|] `shouldRespondWith`
|
||||||
|
[json|[{"x": 10, "y": 5}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "returns single row from table" $
|
it "returns single row from table" $
|
||||||
post "/rpc/single_article?select=id" [json|{"id": 2}|] `shouldRespondWith`
|
post "/rpc/single_article?select=id" [json|{"id": 2}|] `shouldRespondWith`
|
||||||
[json|[{"id": 2}]|]
|
[json|[{"id": 2}]|]
|
||||||
|
|||||||
Vendored
+24
@@ -925,6 +925,19 @@ CREATE FUNCTION setprojects(id_l int, id_h int, name text) RETURNS SETOF project
|
|||||||
update test.projects set name = $3 WHERE id >= $1 AND id <= $2 returning *;
|
update test.projects set name = $3 WHERE id >= $1 AND id <= $2 returning *;
|
||||||
$_$;
|
$_$;
|
||||||
|
|
||||||
|
-- domains on tables are only supported from pg 11 on
|
||||||
|
DO $do$BEGIN
|
||||||
|
IF (SELECT current_setting('server_version_num')::INT >= 110000) THEN
|
||||||
|
CREATE DOMAIN projects_domain AS projects;
|
||||||
|
|
||||||
|
CREATE FUNCTION getproject_domain(id int) RETURNS SETOF projects_domain
|
||||||
|
LANGUAGE sql
|
||||||
|
AS $_$
|
||||||
|
SELECT projects::projects_domain FROM test.projects WHERE id = $1;
|
||||||
|
$_$;
|
||||||
|
END IF;
|
||||||
|
END$do$;
|
||||||
|
|
||||||
create table images (
|
create table images (
|
||||||
name text not null,
|
name text not null,
|
||||||
img bytea not null
|
img bytea not null
|
||||||
@@ -970,6 +983,17 @@ create function test.ret_point_2d() returns test.point_2d as $$
|
|||||||
select row(10, 5)::test.point_2d;
|
select row(10, 5)::test.point_2d;
|
||||||
$$ language sql;
|
$$ language sql;
|
||||||
|
|
||||||
|
-- domains on composite types are only supported from pg 11 on
|
||||||
|
do $do$begin
|
||||||
|
if (SELECT current_setting('server_version_num')::int >= 110000) then
|
||||||
|
create domain test.composite_domain as test.point_2d;
|
||||||
|
|
||||||
|
create function test.ret_composite_domain() returns test.composite_domain as $$
|
||||||
|
select row(10, 5)::test.composite_domain;
|
||||||
|
$$ language sql;
|
||||||
|
end if;
|
||||||
|
end$do$;
|
||||||
|
|
||||||
create type private.point_3d as (x integer, y integer, z integer);
|
create type private.point_3d as (x integer, y integer, z integer);
|
||||||
|
|
||||||
create function test.ret_point_3d() returns private.point_3d as $$
|
create function test.ret_point_3d() returns private.point_3d as $$
|
||||||
|
|||||||
Reference in New Issue
Block a user