refactor: clean allColumns query
* Add OpenAPI test to ensure enum labels are present * Remove unnecessary fk/pk columns cte * Remove unnecessary step for parsing enum labels
This commit is contained in:
committed by
Steve Chavez
parent
d2719420f4
commit
902e4e8921
+58
-101
@@ -36,7 +36,6 @@ import qualified Hasql.Statement as SQL
|
|||||||
import qualified Hasql.Transaction as SQL
|
import qualified Hasql.Transaction as SQL
|
||||||
|
|
||||||
import Contravariant.Extras (contrazip2)
|
import Contravariant.Extras (contrazip2)
|
||||||
import Data.Text (split)
|
|
||||||
import Text.InterpolatedString.Perl6 (q)
|
import Text.InterpolatedString.Perl6 (q)
|
||||||
|
|
||||||
import PostgREST.Config.Database (pgVersionStatement)
|
import PostgREST.Config.Database (pgVersionStatement)
|
||||||
@@ -154,7 +153,7 @@ decodeColumns tables =
|
|||||||
<*> column HD.text
|
<*> column HD.text
|
||||||
<*> nullableColumn HD.int4
|
<*> nullableColumn HD.int4
|
||||||
<*> nullableColumn HD.text
|
<*> nullableColumn HD.text
|
||||||
<*> nullableColumn HD.text
|
<*> arrayColumn HD.text
|
||||||
|
|
||||||
decodeRels :: HD.Result [Relationship]
|
decodeRels :: HD.Result [Relationship]
|
||||||
decodeRels =
|
decodeRels =
|
||||||
@@ -543,7 +542,57 @@ allColumns :: TablesMap -> Bool -> SQL.Statement [Schema] [Column]
|
|||||||
allColumns tabs =
|
allColumns tabs =
|
||||||
SQL.Statement sql (arrayParam HE.text) (decodeColumns tabs)
|
SQL.Statement sql (arrayParam HE.text) (decodeColumns tabs)
|
||||||
where
|
where
|
||||||
|
-- CTE based on information_schema.columns changed: remove the owner filter
|
||||||
sql = [q|
|
sql = [q|
|
||||||
|
WITH
|
||||||
|
columns AS (
|
||||||
|
SELECT
|
||||||
|
nc.nspname::name AS table_schema,
|
||||||
|
c.relname::name AS table_name,
|
||||||
|
a.attname::name AS column_name,
|
||||||
|
d.description AS description,
|
||||||
|
pg_get_expr(ad.adbin, ad.adrelid)::text AS column_default,
|
||||||
|
not (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS is_nullable,
|
||||||
|
CASE
|
||||||
|
WHEN t.typtype = 'd' THEN
|
||||||
|
CASE
|
||||||
|
WHEN bt.typelem <> 0::oid AND bt.typlen = (-1) THEN 'ARRAY'::text
|
||||||
|
WHEN nbt.nspname = 'pg_catalog'::name THEN format_type(t.typbasetype, NULL::integer)
|
||||||
|
ELSE format_type(a.atttypid, a.atttypmod)
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
CASE
|
||||||
|
WHEN t.typelem <> 0::oid AND t.typlen = (-1) THEN 'ARRAY'::text
|
||||||
|
WHEN nt.nspname = 'pg_catalog'::name THEN format_type(a.atttypid, NULL::integer)
|
||||||
|
ELSE format_type(a.atttypid, a.atttypmod)
|
||||||
|
END
|
||||||
|
END::text AS data_type,
|
||||||
|
information_schema._pg_char_max_length(
|
||||||
|
information_schema._pg_truetypid(a.*, t.*),
|
||||||
|
information_schema._pg_truetypmod(a.*, t.*)
|
||||||
|
)::integer AS character_maximum_length,
|
||||||
|
COALESCE(bt.typname, t.typname)::name AS udt_name,
|
||||||
|
a.attnum::integer AS position
|
||||||
|
FROM pg_attribute a
|
||||||
|
LEFT JOIN pg_description AS d
|
||||||
|
ON d.objoid = a.attrelid and d.objsubid = a.attnum
|
||||||
|
LEFT JOIN pg_attrdef ad
|
||||||
|
ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
|
||||||
|
JOIN (pg_class c JOIN pg_namespace nc ON c.relnamespace = nc.oid)
|
||||||
|
ON a.attrelid = c.oid
|
||||||
|
JOIN (pg_type t JOIN pg_namespace nt ON t.typnamespace = nt.oid)
|
||||||
|
ON a.atttypid = t.oid
|
||||||
|
LEFT JOIN (pg_type bt JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid)
|
||||||
|
ON t.typtype = 'd' AND t.typbasetype = bt.oid
|
||||||
|
LEFT JOIN (pg_collation co JOIN pg_namespace nco ON co.collnamespace = nco.oid)
|
||||||
|
ON a.attcollation = co.oid AND (nco.nspname <> 'pg_catalog'::name OR co.collname <> 'default'::name)
|
||||||
|
WHERE
|
||||||
|
NOT pg_is_other_temp_schema(nc.oid)
|
||||||
|
AND a.attnum > 0
|
||||||
|
AND NOT a.attisdropped
|
||||||
|
AND c.relkind in ('r', 'v', 'f', 'm', 'p')
|
||||||
|
AND nc.nspname = ANY($1)
|
||||||
|
)
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
info.table_schema AS schema,
|
info.table_schema AS schema,
|
||||||
info.table_name AS table_name,
|
info.table_name AS table_name,
|
||||||
@@ -553,101 +602,9 @@ allColumns tabs =
|
|||||||
info.data_type AS col_type,
|
info.data_type AS col_type,
|
||||||
info.character_maximum_length AS max_len,
|
info.character_maximum_length AS max_len,
|
||||||
info.column_default AS default_value,
|
info.column_default AS default_value,
|
||||||
array_to_string(enum_info.vals, ',') AS enum,
|
coalesce(enum_info.vals, '{}') AS enum,
|
||||||
info.position
|
info.position
|
||||||
FROM (
|
FROM columns info
|
||||||
-- CTE based on pg_catalog to get PRIMARY/FOREIGN key and UNIQUE columns outside api schema
|
|
||||||
WITH key_columns AS (
|
|
||||||
SELECT
|
|
||||||
r.oid AS r_oid,
|
|
||||||
c.oid AS c_oid,
|
|
||||||
n.nspname,
|
|
||||||
c.relname,
|
|
||||||
r.conname,
|
|
||||||
r.contype,
|
|
||||||
unnest(r.conkey) AS conkey
|
|
||||||
FROM
|
|
||||||
pg_constraint r,
|
|
||||||
pg_class c,
|
|
||||||
pg_namespace n
|
|
||||||
WHERE
|
|
||||||
r.contype IN ('f', 'p', 'u')
|
|
||||||
AND c.relkind IN ('r', 'v', 'f', 'm', 'p')
|
|
||||||
AND r.conrelid = c.oid
|
|
||||||
AND c.relnamespace = n.oid
|
|
||||||
AND n.nspname <> ANY (ARRAY['pg_catalog', 'information_schema'] || $1)
|
|
||||||
),
|
|
||||||
/*
|
|
||||||
-- CTE based on information_schema.columns
|
|
||||||
-- changed:
|
|
||||||
-- remove the owner filter
|
|
||||||
-- limit columns to the ones in the api schema or PK/FK columns
|
|
||||||
*/
|
|
||||||
columns AS (
|
|
||||||
SELECT
|
|
||||||
nc.nspname::name AS table_schema,
|
|
||||||
c.relname::name AS table_name,
|
|
||||||
a.attname::name AS column_name,
|
|
||||||
d.description AS description,
|
|
||||||
pg_get_expr(ad.adbin, ad.adrelid)::text AS column_default,
|
|
||||||
not (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS is_nullable,
|
|
||||||
CASE
|
|
||||||
WHEN t.typtype = 'd' THEN
|
|
||||||
CASE
|
|
||||||
WHEN bt.typelem <> 0::oid AND bt.typlen = (-1) THEN 'ARRAY'::text
|
|
||||||
WHEN nbt.nspname = 'pg_catalog'::name THEN format_type(t.typbasetype, NULL::integer)
|
|
||||||
ELSE format_type(a.atttypid, a.atttypmod)
|
|
||||||
END
|
|
||||||
ELSE
|
|
||||||
CASE
|
|
||||||
WHEN t.typelem <> 0::oid AND t.typlen = (-1) THEN 'ARRAY'::text
|
|
||||||
WHEN nt.nspname = 'pg_catalog'::name THEN format_type(a.atttypid, NULL::integer)
|
|
||||||
ELSE format_type(a.atttypid, a.atttypmod)
|
|
||||||
END
|
|
||||||
END::text AS data_type,
|
|
||||||
information_schema._pg_char_max_length(
|
|
||||||
information_schema._pg_truetypid(a.*, t.*),
|
|
||||||
information_schema._pg_truetypmod(a.*, t.*)
|
|
||||||
)::integer AS character_maximum_length,
|
|
||||||
COALESCE(bt.typname, t.typname)::name AS udt_name,
|
|
||||||
a.attnum::integer AS position
|
|
||||||
FROM pg_attribute a
|
|
||||||
LEFT JOIN key_columns kc
|
|
||||||
ON kc.conkey = a.attnum AND kc.c_oid = a.attrelid
|
|
||||||
LEFT JOIN pg_description AS d
|
|
||||||
ON d.objoid = a.attrelid and d.objsubid = a.attnum
|
|
||||||
LEFT JOIN pg_attrdef ad
|
|
||||||
ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
|
|
||||||
JOIN (pg_class c JOIN pg_namespace nc ON c.relnamespace = nc.oid)
|
|
||||||
ON a.attrelid = c.oid
|
|
||||||
JOIN (pg_type t JOIN pg_namespace nt ON t.typnamespace = nt.oid)
|
|
||||||
ON a.atttypid = t.oid
|
|
||||||
LEFT JOIN (pg_type bt JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid)
|
|
||||||
ON t.typtype = 'd' AND t.typbasetype = bt.oid
|
|
||||||
LEFT JOIN (pg_collation co JOIN pg_namespace nco ON co.collnamespace = nco.oid)
|
|
||||||
ON a.attcollation = co.oid AND (nco.nspname <> 'pg_catalog'::name OR co.collname <> 'default'::name)
|
|
||||||
WHERE
|
|
||||||
NOT pg_is_other_temp_schema(nc.oid)
|
|
||||||
AND a.attnum > 0
|
|
||||||
AND NOT a.attisdropped
|
|
||||||
AND c.relkind in ('r', 'v', 'f', 'm', 'p')
|
|
||||||
-- Filter only columns that are FK/PK or in the api schema:
|
|
||||||
AND (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL)
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
table_schema,
|
|
||||||
table_name,
|
|
||||||
column_name,
|
|
||||||
description,
|
|
||||||
is_nullable,
|
|
||||||
data_type,
|
|
||||||
character_maximum_length,
|
|
||||||
column_default,
|
|
||||||
udt_name,
|
|
||||||
position
|
|
||||||
FROM columns
|
|
||||||
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
||||||
) AS info
|
|
||||||
LEFT OUTER JOIN (
|
LEFT OUTER JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
n.nspname AS s,
|
n.nspname AS s,
|
||||||
@@ -658,19 +615,19 @@ allColumns tabs =
|
|||||||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||||
GROUP BY s,n
|
GROUP BY s,n
|
||||||
) AS enum_info ON (info.udt_name = enum_info.n)
|
) AS enum_info ON (info.udt_name = enum_info.n)
|
||||||
ORDER BY schema, position |]
|
WHERE info.table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||||
|
ORDER BY schema, position;
|
||||||
|
|]
|
||||||
|
|
||||||
columnFromRow :: TablesMap ->
|
columnFromRow :: TablesMap ->
|
||||||
(Text, Text, Text,
|
(Text, Text, Text,
|
||||||
Maybe Text, Bool, Text,
|
Maybe Text, Bool, Text,
|
||||||
Maybe Int32, Maybe Text, Maybe Text)
|
Maybe Int32, Maybe Text, [Text])
|
||||||
-> Maybe Column
|
-> Maybe Column
|
||||||
columnFromRow tabs (s, t, n, desc, nul, typ, l, d, e) = buildColumn <$> table
|
columnFromRow tabs (s, t, n, desc, nul, typ, l, d, e) = buildColumn <$> table
|
||||||
where
|
where
|
||||||
buildColumn tbl = Column tbl n desc nul typ l d (parseEnum e)
|
buildColumn tbl = Column tbl n desc nul typ l d e
|
||||||
table = M.lookup (QualifiedIdentifier s t) tabs
|
table = M.lookup (QualifiedIdentifier s t) tabs
|
||||||
parseEnum :: Maybe Text -> [Text]
|
|
||||||
parseEnum = maybe [] (split (==','))
|
|
||||||
|
|
||||||
allM2ORels :: PgVersion -> Bool -> SQL.Statement () [Relationship]
|
allM2ORels :: PgVersion -> Bool -> SQL.Statement () [Relationship]
|
||||||
allM2ORels pgVer =
|
allM2ORels pgVer =
|
||||||
|
|||||||
@@ -514,6 +514,24 @@ spec actualPgVersion = describe "OpenAPI" $ do
|
|||||||
|
|
||||||
types `shouldBe` Just "13:00:00"
|
types `shouldBe` Just "13:00:00"
|
||||||
|
|
||||||
|
it "enum" $ do
|
||||||
|
r <- simpleBody <$> get "/"
|
||||||
|
|
||||||
|
let types = r ^? key "definitions" . key "menagerie" . key "properties" . key "enum"
|
||||||
|
|
||||||
|
liftIO $
|
||||||
|
|
||||||
|
types `shouldBe` Just [aesonQQ|
|
||||||
|
{
|
||||||
|
"format": "test.enum_menagerie_type",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
describe "RPC" $ do
|
describe "RPC" $ do
|
||||||
|
|
||||||
it "includes function summary/description and body schema for arguments" $ do
|
it "includes function summary/description and body schema for arguments" $ do
|
||||||
|
|||||||
Reference in New Issue
Block a user