refactor: merge Column list into Table
* Remove Table from Column * Correct OpenAPI test where the schema was appearing for the enum unnecessarily * Increase memory-tests consumption by 2M Seems the Hasql decoder consumes more memory now that the allTables results also have [Column] inside.
This commit is contained in:
committed by
Steve Chavez
parent
902e4e8921
commit
778b8d074d
@@ -485,7 +485,7 @@ handleOpenApi headersOnly tSchema (RequestContext conf@AppConfig{..} dbStructure
|
|||||||
lift $ case configOpenApiMode of
|
lift $ case configOpenApiMode of
|
||||||
OAFollowPriv ->
|
OAFollowPriv ->
|
||||||
OpenAPI.encode conf dbStructure
|
OpenAPI.encode conf dbStructure
|
||||||
<$> SQL.statement tSchema (DbStructure.accessibleTables ctxPgVersion configDbPreparedStatements)
|
<$> SQL.statement [tSchema] (DbStructure.accessibleTables ctxPgVersion configDbPreparedStatements)
|
||||||
<*> SQL.statement tSchema (DbStructure.accessibleProcs ctxPgVersion configDbPreparedStatements)
|
<*> SQL.statement tSchema (DbStructure.accessibleProcs ctxPgVersion configDbPreparedStatements)
|
||||||
<*> SQL.statement tSchema (DbStructure.schemaDescription configDbPreparedStatements)
|
<*> SQL.statement tSchema (DbStructure.schemaDescription configDbPreparedStatements)
|
||||||
OAIgnorePriv ->
|
OAIgnorePriv ->
|
||||||
|
|||||||
+108
-132
@@ -25,7 +25,6 @@ module PostgREST.DbStructure
|
|||||||
, accessibleProcs
|
, accessibleProcs
|
||||||
, findIfView
|
, findIfView
|
||||||
, schemaDescription
|
, schemaDescription
|
||||||
, tableCols
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
@@ -43,7 +42,7 @@ import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
|||||||
pgVersion110)
|
pgVersion110)
|
||||||
import PostgREST.DbStructure.Identifiers (FieldName,
|
import PostgREST.DbStructure.Identifiers (FieldName,
|
||||||
QualifiedIdentifier (..),
|
QualifiedIdentifier (..),
|
||||||
Schema, TableName)
|
Schema)
|
||||||
import PostgREST.DbStructure.Proc (PgType (..),
|
import PostgREST.DbStructure.Proc (PgType (..),
|
||||||
ProcDescription (..),
|
ProcDescription (..),
|
||||||
ProcParam (..),
|
ProcParam (..),
|
||||||
@@ -60,16 +59,11 @@ import Protolude
|
|||||||
|
|
||||||
data DbStructure = DbStructure
|
data DbStructure = DbStructure
|
||||||
{ dbTables :: TablesMap
|
{ dbTables :: TablesMap
|
||||||
, dbColumns :: [Column]
|
|
||||||
, dbRelationships :: [Relationship]
|
, dbRelationships :: [Relationship]
|
||||||
, dbProcs :: ProcsMap
|
, dbProcs :: ProcsMap
|
||||||
}
|
}
|
||||||
deriving (Generic, JSON.ToJSON)
|
deriving (Generic, JSON.ToJSON)
|
||||||
|
|
||||||
-- TODO Table could hold references to all its Columns
|
|
||||||
tableCols :: DbStructure -> Schema -> TableName -> [Column]
|
|
||||||
tableCols dbs tSchema tName = filter (\Column{colTable=Table{tableSchema=s, tableName=t}} -> s==tSchema && t==tName) $ dbColumns dbs
|
|
||||||
|
|
||||||
findIfView :: QualifiedIdentifier -> TablesMap -> Bool
|
findIfView :: QualifiedIdentifier -> TablesMap -> Bool
|
||||||
findIfView identifier tbls = maybe False tableIsView $ M.lookup identifier tbls
|
findIfView identifier tbls = maybe False tableIsView $ M.lookup identifier tbls
|
||||||
|
|
||||||
@@ -94,8 +88,7 @@ queryDbStructure :: [Schema] -> [Schema] -> Bool -> SQL.Transaction DbStructure
|
|||||||
queryDbStructure schemas extraSearchPath prepared = do
|
queryDbStructure schemas extraSearchPath prepared = do
|
||||||
SQL.sql "set local schema ''" -- This voids the search path. The following queries need this for getting the fully qualified name(schema.name) of every db object
|
SQL.sql "set local schema ''" -- This voids the search path. The following queries need this for getting the fully qualified name(schema.name) of every db object
|
||||||
pgVer <- SQL.statement mempty pgVersionStatement
|
pgVer <- SQL.statement mempty pgVersionStatement
|
||||||
tabs <- SQL.statement mempty $ allTables pgVer prepared
|
tabs <- SQL.statement schemas $ allTables pgVer prepared
|
||||||
cols <- SQL.statement schemas $ allColumns tabs prepared
|
|
||||||
keyDeps <- SQL.statement (schemas, extraSearchPath) $ allViewsKeyDependencies prepared
|
keyDeps <- SQL.statement (schemas, extraSearchPath) $ allViewsKeyDependencies prepared
|
||||||
m2oRels <- SQL.statement mempty $ allM2ORels pgVer prepared
|
m2oRels <- SQL.statement mempty $ allM2ORels pgVer prepared
|
||||||
procs <- SQL.statement schemas $ allProcs pgVer prepared
|
procs <- SQL.statement schemas $ allProcs pgVer prepared
|
||||||
@@ -105,7 +98,6 @@ queryDbStructure schemas extraSearchPath prepared = do
|
|||||||
|
|
||||||
return $ removeInternal schemas $ DbStructure {
|
return $ removeInternal schemas $ DbStructure {
|
||||||
dbTables = tabsWViewsPks
|
dbTables = tabsWViewsPks
|
||||||
, dbColumns = cols
|
|
||||||
, dbRelationships = rels
|
, dbRelationships = rels
|
||||||
, dbProcs = procs
|
, dbProcs = procs
|
||||||
}
|
}
|
||||||
@@ -115,7 +107,6 @@ removeInternal :: [Schema] -> DbStructure -> DbStructure
|
|||||||
removeInternal schemas dbStruct =
|
removeInternal schemas dbStruct =
|
||||||
DbStructure {
|
DbStructure {
|
||||||
dbTables = M.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch `elem` schemas) $ dbTables dbStruct
|
dbTables = M.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch `elem` schemas) $ dbTables dbStruct
|
||||||
, dbColumns = filter (\x -> tableSchema (colTable x) `elem` schemas) (dbColumns dbStruct)
|
|
||||||
, dbRelationships = filter (\x -> qiSchema (relTable x) `elem` schemas &&
|
, dbRelationships = filter (\x -> qiSchema (relTable x) `elem` schemas &&
|
||||||
qiSchema (relForeignTable x) `elem` schemas &&
|
qiSchema (relForeignTable x) `elem` schemas &&
|
||||||
not (hasInternalJunction x)) $ dbRelationships dbStruct
|
not (hasInternalJunction x)) $ dbRelationships dbStruct
|
||||||
@@ -130,30 +121,24 @@ decodeTables :: HD.Result TablesMap
|
|||||||
decodeTables =
|
decodeTables =
|
||||||
M.fromList . map (\tbl@Table{tableSchema, tableName} -> (QualifiedIdentifier tableSchema tableName, tbl)) <$> HD.rowList tblRow
|
M.fromList . map (\tbl@Table{tableSchema, tableName} -> (QualifiedIdentifier tableSchema tableName, tbl)) <$> HD.rowList tblRow
|
||||||
where
|
where
|
||||||
tblRow = Table <$> column HD.text
|
tblRow = Table
|
||||||
<*> column HD.text
|
<$> column HD.text
|
||||||
<*> nullableColumn HD.text
|
<*> column HD.text
|
||||||
<*> column HD.bool
|
<*> nullableColumn HD.text
|
||||||
<*> column HD.bool
|
<*> column HD.bool
|
||||||
<*> column HD.bool
|
<*> column HD.bool
|
||||||
<*> column HD.bool
|
<*> column HD.bool
|
||||||
<*> arrayColumn HD.text
|
<*> column HD.bool
|
||||||
|
<*> arrayColumn HD.text
|
||||||
decodeColumns :: TablesMap -> HD.Result [Column]
|
<*> compositeArrayColumn
|
||||||
decodeColumns tables =
|
(Column
|
||||||
mapMaybe (columnFromRow tables) <$> HD.rowList colRow
|
<$> compositeField HD.text
|
||||||
where
|
<*> nullableCompositeField HD.text
|
||||||
colRow =
|
<*> compositeField HD.bool
|
||||||
(,,,,,,,,)
|
<*> compositeField HD.text
|
||||||
<$> column HD.text
|
<*> nullableCompositeField HD.int4
|
||||||
<*> column HD.text
|
<*> nullableCompositeField HD.text
|
||||||
<*> column HD.text
|
<*> compositeFieldArray HD.text)
|
||||||
<*> nullableColumn HD.text
|
|
||||||
<*> column HD.bool
|
|
||||||
<*> column HD.text
|
|
||||||
<*> nullableColumn HD.int4
|
|
||||||
<*> nullableColumn HD.text
|
|
||||||
<*> arrayColumn HD.text
|
|
||||||
|
|
||||||
decodeRels :: HD.Result [Relationship]
|
decodeRels :: HD.Result [Relationship]
|
||||||
decodeRels =
|
decodeRels =
|
||||||
@@ -330,9 +315,9 @@ schemaDescription =
|
|||||||
where
|
where
|
||||||
n.nspname = $1 |]
|
n.nspname = $1 |]
|
||||||
|
|
||||||
accessibleTables :: PgVersion -> Bool -> SQL.Statement Schema TablesMap
|
accessibleTables :: PgVersion -> Bool -> SQL.Statement [Schema] TablesMap
|
||||||
accessibleTables pgVer =
|
accessibleTables pgVer =
|
||||||
SQL.Statement sql (param HE.text) decodeTables
|
SQL.Statement sql (arrayParam HE.text) decodeTables
|
||||||
where
|
where
|
||||||
sql = tablesSqlQuery False pgVer
|
sql = tablesSqlQuery False pgVer
|
||||||
|
|
||||||
@@ -403,9 +388,9 @@ addViewPrimaryKeys tabs keyDeps =
|
|||||||
maybe [] (\(ViewKeyDependency _ _ _ _ pkCols) -> snd <$> pkCols) $
|
maybe [] (\(ViewKeyDependency _ _ _ _ pkCols) -> snd <$> pkCols) $
|
||||||
find (\(ViewKeyDependency _ viewQi _ dep _) -> dep == PKDep && viewQi == QualifiedIdentifier sch vw) keyDeps
|
find (\(ViewKeyDependency _ viewQi _ dep _) -> dep == PKDep && viewQi == QualifiedIdentifier sch vw) keyDeps
|
||||||
|
|
||||||
allTables :: PgVersion -> Bool -> SQL.Statement () TablesMap
|
allTables :: PgVersion -> Bool -> SQL.Statement [Schema] TablesMap
|
||||||
allTables pgVer =
|
allTables pgVer =
|
||||||
SQL.Statement sql HE.noParams decodeTables
|
SQL.Statement sql (arrayParam HE.text) decodeTables
|
||||||
where
|
where
|
||||||
sql = tablesSqlQuery True pgVer
|
sql = tablesSqlQuery True pgVer
|
||||||
|
|
||||||
@@ -417,6 +402,80 @@ tablesSqlQuery getAll pgVer =
|
|||||||
-- (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text));
|
-- (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text));
|
||||||
[q|
|
[q|
|
||||||
WITH
|
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)
|
||||||
|
),
|
||||||
|
columns_agg AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
info.table_schema AS table_schema,
|
||||||
|
info.table_name AS table_name,
|
||||||
|
array_agg(row(
|
||||||
|
info.column_name,
|
||||||
|
info.description,
|
||||||
|
info.is_nullable::boolean,
|
||||||
|
info.data_type,
|
||||||
|
info.character_maximum_length,
|
||||||
|
info.column_default,
|
||||||
|
coalesce(enum_info.vals, '{}')) order by info.position) as columns
|
||||||
|
FROM columns info
|
||||||
|
LEFT OUTER JOIN (
|
||||||
|
SELECT
|
||||||
|
n.nspname AS s,
|
||||||
|
t.typname AS n,
|
||||||
|
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS vals
|
||||||
|
FROM pg_type t
|
||||||
|
JOIN pg_enum e ON t.oid = e.enumtypid
|
||||||
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||||
|
GROUP BY s,n
|
||||||
|
) AS enum_info ON info.udt_name = enum_info.n
|
||||||
|
WHERE info.table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||||
|
GROUP BY info.table_schema, info.table_name
|
||||||
|
),
|
||||||
tbl_constraints AS (
|
tbl_constraints AS (
|
||||||
SELECT
|
SELECT
|
||||||
c.conname::name AS constraint_name,
|
c.conname::name AS constraint_name,
|
||||||
@@ -518,11 +577,13 @@ tablesSqlQuery getAll pgVer =
|
|||||||
AND (pg_relation_is_updatable(c.oid::regclass, TRUE) & 16) = 16
|
AND (pg_relation_is_updatable(c.oid::regclass, TRUE) & 16) = 16
|
||||||
)
|
)
|
||||||
) AS deletable,
|
) AS deletable,
|
||||||
coalesce(tpks.pk_cols, '{}') as pk_cols
|
coalesce(tpks.pk_cols, '{}') as pk_cols,
|
||||||
|
coalesce(cols_agg.columns, '{}') as columns
|
||||||
FROM pg_class c
|
FROM pg_class c
|
||||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||||
LEFT JOIN pg_description d on d.objoid = c.oid and d.objsubid = 0
|
LEFT JOIN pg_description d on d.objoid = c.oid and d.objsubid = 0
|
||||||
LEFT JOIN tbl_pk_cols tpks ON n.nspname = tpks.table_schema AND c.relname = tpks.table_name
|
LEFT JOIN tbl_pk_cols tpks ON n.nspname = tpks.table_schema AND c.relname = tpks.table_name
|
||||||
|
LEFT JOIN columns_agg cols_agg ON n.nspname = cols_agg.table_schema AND c.relname = cols_agg.table_name
|
||||||
WHERE c.relkind IN ('v','r','m','f','p')
|
WHERE c.relkind IN ('v','r','m','f','p')
|
||||||
AND n.nspname NOT IN ('pg_catalog', 'information_schema') |] <>
|
AND n.nspname NOT IN ('pg_catalog', 'information_schema') |] <>
|
||||||
relIsPartition <>
|
relIsPartition <>
|
||||||
@@ -530,7 +591,7 @@ tablesSqlQuery getAll pgVer =
|
|||||||
"ORDER BY table_schema, table_name"
|
"ORDER BY table_schema, table_name"
|
||||||
where
|
where
|
||||||
fltTables = if getAll then mempty else [q|
|
fltTables = if getAll then mempty else [q|
|
||||||
AND n.nspname = $1
|
AND n.nspname = ANY($1)
|
||||||
AND (
|
AND (
|
||||||
pg_has_role(c.relowner, 'USAGE')
|
pg_has_role(c.relowner, 'USAGE')
|
||||||
or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
|
or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
|
||||||
@@ -538,97 +599,6 @@ tablesSqlQuery getAll pgVer =
|
|||||||
)|]
|
)|]
|
||||||
relIsPartition = if pgVer >= pgVersion100 then " AND not c.relispartition " else mempty
|
relIsPartition = if pgVer >= pgVersion100 then " AND not c.relispartition " else mempty
|
||||||
|
|
||||||
allColumns :: TablesMap -> Bool -> SQL.Statement [Schema] [Column]
|
|
||||||
allColumns tabs =
|
|
||||||
SQL.Statement sql (arrayParam HE.text) (decodeColumns tabs)
|
|
||||||
where
|
|
||||||
-- CTE based on information_schema.columns changed: remove the owner filter
|
|
||||||
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
|
|
||||||
info.table_schema AS schema,
|
|
||||||
info.table_name AS table_name,
|
|
||||||
info.column_name AS name,
|
|
||||||
info.description AS description,
|
|
||||||
info.is_nullable::boolean AS nullable,
|
|
||||||
info.data_type AS col_type,
|
|
||||||
info.character_maximum_length AS max_len,
|
|
||||||
info.column_default AS default_value,
|
|
||||||
coalesce(enum_info.vals, '{}') AS enum,
|
|
||||||
info.position
|
|
||||||
FROM columns info
|
|
||||||
LEFT OUTER JOIN (
|
|
||||||
SELECT
|
|
||||||
n.nspname AS s,
|
|
||||||
t.typname AS n,
|
|
||||||
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS vals
|
|
||||||
FROM pg_type t
|
|
||||||
JOIN pg_enum e ON t.oid = e.enumtypid
|
|
||||||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
||||||
GROUP BY s,n
|
|
||||||
) AS enum_info ON (info.udt_name = enum_info.n)
|
|
||||||
WHERE info.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
||||||
ORDER BY schema, position;
|
|
||||||
|]
|
|
||||||
|
|
||||||
columnFromRow :: TablesMap ->
|
|
||||||
(Text, Text, Text,
|
|
||||||
Maybe Text, Bool, Text,
|
|
||||||
Maybe Int32, Maybe Text, [Text])
|
|
||||||
-> Maybe Column
|
|
||||||
columnFromRow tabs (s, t, n, desc, nul, typ, l, d, e) = buildColumn <$> table
|
|
||||||
where
|
|
||||||
buildColumn tbl = Column tbl n desc nul typ l d e
|
|
||||||
table = M.lookup (QualifiedIdentifier s t) tabs
|
|
||||||
|
|
||||||
allM2ORels :: PgVersion -> Bool -> SQL.Statement () [Relationship]
|
allM2ORels :: PgVersion -> Bool -> SQL.Statement () [Relationship]
|
||||||
allM2ORels pgVer =
|
allM2ORels pgVer =
|
||||||
SQL.Statement sql HE.noParams decodeRels
|
SQL.Statement sql HE.noParams decodeRels
|
||||||
@@ -834,6 +804,12 @@ compositeArrayColumn = arrayColumn . HD.composite
|
|||||||
compositeField :: HD.Value a -> HD.Composite a
|
compositeField :: HD.Value a -> HD.Composite a
|
||||||
compositeField = HD.field . HD.nonNullable
|
compositeField = HD.field . HD.nonNullable
|
||||||
|
|
||||||
|
nullableCompositeField :: HD.Value a -> HD.Composite (Maybe a)
|
||||||
|
nullableCompositeField = HD.field . HD.nullable
|
||||||
|
|
||||||
|
compositeFieldArray :: HD.Value a -> HD.Composite [a]
|
||||||
|
compositeFieldArray = HD.field . HD.nonNullable . HD.listArray . HD.nonNullable
|
||||||
|
|
||||||
column :: HD.Value a -> HD.Row a
|
column :: HD.Value a -> HD.Row a
|
||||||
column = HD.column . HD.nonNullable
|
column = HD.column . HD.nonNullable
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ data Table = Table
|
|||||||
, tableUpdatable :: Bool
|
, tableUpdatable :: Bool
|
||||||
, tableDeletable :: Bool
|
, tableDeletable :: Bool
|
||||||
, tablePKCols :: [FieldName]
|
, tablePKCols :: [FieldName]
|
||||||
|
, tableColumns :: [Column]
|
||||||
}
|
}
|
||||||
deriving (Show, Ord, Generic, JSON.ToJSON)
|
deriving (Show, Ord, Generic, JSON.ToJSON)
|
||||||
|
|
||||||
@@ -35,8 +36,7 @@ instance Eq Table where
|
|||||||
Table{tableSchema=s1,tableName=n1} == Table{tableSchema=s2,tableName=n2} = s1 == s2 && n1 == n2
|
Table{tableSchema=s1,tableName=n1} == Table{tableSchema=s2,tableName=n2} = s1 == s2 && n1 == n2
|
||||||
|
|
||||||
data Column = Column
|
data Column = Column
|
||||||
{ colTable :: Table
|
{ colName :: FieldName
|
||||||
, colName :: FieldName
|
|
||||||
, colDescription :: Maybe Text
|
, colDescription :: Maybe Text
|
||||||
, colNullable :: Bool
|
, colNullable :: Bool
|
||||||
, colType :: Text
|
, colType :: Text
|
||||||
@@ -44,9 +44,6 @@ data Column = Column
|
|||||||
, colDefault :: Maybe Text
|
, colDefault :: Maybe Text
|
||||||
, colEnum :: [Text]
|
, colEnum :: [Text]
|
||||||
}
|
}
|
||||||
deriving (Ord, Generic, JSON.ToJSON)
|
deriving (Eq, Show, Ord, Generic, JSON.ToJSON)
|
||||||
|
|
||||||
instance Eq Column where
|
|
||||||
Column{colTable=t1,colName=n1} == Column{colTable=t2,colName=n2} = t1 == t2 && n1 == n2
|
|
||||||
|
|
||||||
type TablesMap = M.HashMap QualifiedIdentifier Table
|
type TablesMap = M.HashMap QualifiedIdentifier Table
|
||||||
|
|||||||
+17
-24
@@ -26,8 +26,7 @@ import Data.Swagger
|
|||||||
|
|
||||||
import PostgREST.Config (AppConfig (..), Proxy (..),
|
import PostgREST.Config (AppConfig (..), Proxy (..),
|
||||||
isMalformedProxyUri, toURI)
|
isMalformedProxyUri, toURI)
|
||||||
import PostgREST.DbStructure (DbStructure (..),
|
import PostgREST.DbStructure (DbStructure (..))
|
||||||
tableCols)
|
|
||||||
import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..))
|
import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..))
|
||||||
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
||||||
ProcParam (..))
|
ProcParam (..))
|
||||||
@@ -47,7 +46,7 @@ encode conf dbStructure tables procs schemaDescription =
|
|||||||
postgrestSpec
|
postgrestSpec
|
||||||
(dbRelationships dbStructure)
|
(dbRelationships dbStructure)
|
||||||
(concat $ M.elems procs)
|
(concat $ M.elems procs)
|
||||||
(openApiTableInfo dbStructure <$> (snd <$> M.toList tables))
|
(snd <$> M.toList tables)
|
||||||
(proxyUri conf)
|
(proxyUri conf)
|
||||||
schemaDescription
|
schemaDescription
|
||||||
|
|
||||||
@@ -80,17 +79,17 @@ parseDefault colType colDefault =
|
|||||||
where
|
where
|
||||||
wrapInQuotations text = "\"" <> text <> "\""
|
wrapInQuotations text = "\"" <> text <> "\""
|
||||||
|
|
||||||
makeTableDef :: [Relationship] -> (Table, [Column]) -> (Text, Schema)
|
makeTableDef :: [Relationship] -> Table -> (Text, Schema)
|
||||||
makeTableDef rels (t, cs) =
|
makeTableDef rels t =
|
||||||
let tn = tableName t in
|
let tn = tableName t in
|
||||||
(tn, (mempty :: Schema)
|
(tn, (mempty :: Schema)
|
||||||
& description .~ tableDescription t
|
& description .~ tableDescription t
|
||||||
& type_ ?~ SwaggerObject
|
& type_ ?~ SwaggerObject
|
||||||
& properties .~ fromList (fmap (makeProperty rels) cs)
|
& properties .~ fromList (makeProperty t rels <$> tableColumns t)
|
||||||
& required .~ fmap colName (filter (not . colNullable) cs))
|
& required .~ fmap colName (filter (not . colNullable) $ tableColumns t))
|
||||||
|
|
||||||
makeProperty :: [Relationship] -> Column -> (Text, Referenced Schema)
|
makeProperty :: Table -> [Relationship] -> Column -> (Text, Referenced Schema)
|
||||||
makeProperty rels col = (colName col, Inline s)
|
makeProperty tbl rels col = (colName col, Inline s)
|
||||||
where
|
where
|
||||||
e = if null $ colEnum col then Nothing else JSON.decode $ JSON.encode $ colEnum col
|
e = if null $ colEnum col then Nothing else JSON.decode $ JSON.encode $ colEnum col
|
||||||
fk :: Maybe Text
|
fk :: Maybe Text
|
||||||
@@ -107,7 +106,7 @@ makeProperty rels col = (colName col, Inline s)
|
|||||||
in
|
in
|
||||||
(\(a, b) -> T.intercalate "" ["This is a Foreign Key to `", a, ".", b, "`.<fk table='", a, "' column='", b, "'/>"]) <$> fTblCol
|
(\(a, b) -> T.intercalate "" ["This is a Foreign Key to `", a, ".", b, "`.<fk table='", a, "' column='", b, "'/>"]) <$> fTblCol
|
||||||
pk :: Bool
|
pk :: Bool
|
||||||
pk = colName col `elem` tablePKCols (colTable col)
|
pk = colName col `elem` tablePKCols tbl
|
||||||
n = catMaybes
|
n = catMaybes
|
||||||
[ Just "Note:"
|
[ Just "Note:"
|
||||||
, if pk then Just "This is a Primary Key.<pk/>" else Nothing
|
, if pk then Just "This is a Primary Key.<pk/>" else Nothing
|
||||||
@@ -162,7 +161,7 @@ makeProcParam pd =
|
|||||||
, Ref $ Reference "preferParams"
|
, Ref $ Reference "preferParams"
|
||||||
]
|
]
|
||||||
|
|
||||||
makeParamDefs :: [(Table, [Column])] -> [(Text, Param)]
|
makeParamDefs :: [Table] -> [(Text, Param)]
|
||||||
makeParamDefs ti =
|
makeParamDefs ti =
|
||||||
[ ("preferParams", makePreferParam ["params=single-object"])
|
[ ("preferParams", makePreferParam ["params=single-object"])
|
||||||
, ("preferReturn", makePreferParam ["return=representation", "return=minimal", "return=none"])
|
, ("preferReturn", makePreferParam ["return=representation", "return=minimal", "return=none"])
|
||||||
@@ -218,8 +217,8 @@ makeParamDefs ti =
|
|||||||
& in_ .~ ParamQuery
|
& in_ .~ ParamQuery
|
||||||
& type_ ?~ SwaggerString))
|
& type_ ?~ SwaggerString))
|
||||||
]
|
]
|
||||||
<> concat [ makeObjectBody (tableName t) : makeRowFilters (tableName t) cs
|
<> concat [ makeObjectBody (tableName t) : makeRowFilters (tableName t) (tableColumns t)
|
||||||
| (t, cs) <- ti
|
| t <- ti
|
||||||
]
|
]
|
||||||
|
|
||||||
makeObjectBody :: Text -> (Text, Param)
|
makeObjectBody :: Text -> (Text, Param)
|
||||||
@@ -244,8 +243,8 @@ makeRowFilter tn c =
|
|||||||
makeRowFilters :: Text -> [Column] -> [(Text, Param)]
|
makeRowFilters :: Text -> [Column] -> [(Text, Param)]
|
||||||
makeRowFilters tn = fmap (makeRowFilter tn)
|
makeRowFilters tn = fmap (makeRowFilter tn)
|
||||||
|
|
||||||
makePathItem :: (Table, [Column]) -> (FilePath, PathItem)
|
makePathItem :: Table -> (FilePath, PathItem)
|
||||||
makePathItem (t, cs) = ("/" ++ T.unpack tn, p $ tableInsertable t || tableUpdatable t || tableDeletable t)
|
makePathItem t = ("/" ++ T.unpack tn, p $ tableInsertable t || tableUpdatable t || tableDeletable t)
|
||||||
where
|
where
|
||||||
-- Use first line of table description as summary; rest as description (if present)
|
-- Use first line of table description as summary; rest as description (if present)
|
||||||
-- We strip leading newlines from description so that users can include a blank line between summary and description
|
-- We strip leading newlines from description so that users can include a blank line between summary and description
|
||||||
@@ -279,7 +278,7 @@ makePathItem (t, cs) = ("/" ++ T.unpack tn, p $ tableInsertable t || tableUpdata
|
|||||||
p False = pr
|
p False = pr
|
||||||
p True = pw
|
p True = pw
|
||||||
tn = tableName t
|
tn = tableName t
|
||||||
rs = [ T.intercalate "." ["rowFilter", tn, colName c ] | c <- cs ]
|
rs = [ T.intercalate "." ["rowFilter", tn, colName c ] | c <- tableColumns t ]
|
||||||
ref = Ref . Reference
|
ref = Ref . Reference
|
||||||
|
|
||||||
makeProcPathItem :: ProcDescription -> (FilePath, PathItem)
|
makeProcPathItem :: ProcDescription -> (FilePath, PathItem)
|
||||||
@@ -309,7 +308,7 @@ makeRootPathItem = ("/", p)
|
|||||||
pr = (mempty :: PathItem) & get ?~ getOp
|
pr = (mempty :: PathItem) & get ?~ getOp
|
||||||
p = pr
|
p = pr
|
||||||
|
|
||||||
makePathItems :: [ProcDescription] -> [(Table, [Column])] -> InsOrdHashMap FilePath PathItem
|
makePathItems :: [ProcDescription] -> [Table] -> InsOrdHashMap FilePath PathItem
|
||||||
makePathItems pds ti = fromList $ makeRootPathItem :
|
makePathItems pds ti = fromList $ makeRootPathItem :
|
||||||
fmap makePathItem ti ++ fmap makeProcPathItem pds
|
fmap makePathItem ti ++ fmap makeProcPathItem pds
|
||||||
|
|
||||||
@@ -321,7 +320,7 @@ escapeHostName "*6" = "0.0.0.0"
|
|||||||
escapeHostName "!6" = "0.0.0.0"
|
escapeHostName "!6" = "0.0.0.0"
|
||||||
escapeHostName h = h
|
escapeHostName h = h
|
||||||
|
|
||||||
postgrestSpec :: [Relationship] -> [ProcDescription] -> [(Table, [Column])] -> (Text, Text, Integer, Text) -> Maybe Text -> Swagger
|
postgrestSpec :: [Relationship] -> [ProcDescription] -> [Table] -> (Text, Text, Integer, Text) -> Maybe Text -> Swagger
|
||||||
postgrestSpec rels pds ti (s, h, p, b) sd = (mempty :: Swagger)
|
postgrestSpec rels pds ti (s, h, p, b) sd = (mempty :: Swagger)
|
||||||
& basePath ?~ T.unpack b
|
& basePath ?~ T.unpack b
|
||||||
& schemes ?~ [s']
|
& schemes ?~ [s']
|
||||||
@@ -379,9 +378,3 @@ proxyUri AppConfig{..} =
|
|||||||
(proxyScheme, proxyHost, proxyPort, proxyPath)
|
(proxyScheme, proxyHost, proxyPort, proxyPath)
|
||||||
Nothing ->
|
Nothing ->
|
||||||
("http", configServerHost, toInteger configServerPort, "/")
|
("http", configServerHost, toInteger configServerPort, "/")
|
||||||
|
|
||||||
openApiTableInfo :: DbStructure -> Table -> (Table, [Column])
|
|
||||||
openApiTableInfo dbStructure table =
|
|
||||||
( table
|
|
||||||
, tableCols dbStructure (tableSchema table) (tableName table)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -102,21 +102,21 @@ postJsonArrayTest(){
|
|||||||
|
|
||||||
echo "Running memory usage tests.."
|
echo "Running memory usage tests.."
|
||||||
|
|
||||||
jsonKeyTest "1M" "POST" "/rpc/leak?columns=blob" "13M"
|
jsonKeyTest "1M" "POST" "/rpc/leak?columns=blob" "15M"
|
||||||
jsonKeyTest "1M" "POST" "/leak?columns=blob" "13M"
|
jsonKeyTest "1M" "POST" "/leak?columns=blob" "15M"
|
||||||
jsonKeyTest "1M" "PATCH" "/leak?id=eq.1&columns=blob" "13M"
|
jsonKeyTest "1M" "PATCH" "/leak?id=eq.1&columns=blob" "15M"
|
||||||
|
|
||||||
jsonKeyTest "10M" "POST" "/rpc/leak?columns=blob" "41M"
|
jsonKeyTest "10M" "POST" "/rpc/leak?columns=blob" "43M"
|
||||||
jsonKeyTest "10M" "POST" "/leak?columns=blob" "41M"
|
jsonKeyTest "10M" "POST" "/leak?columns=blob" "43M"
|
||||||
jsonKeyTest "10M" "PATCH" "/leak?id=eq.1&columns=blob" "41M"
|
jsonKeyTest "10M" "PATCH" "/leak?id=eq.1&columns=blob" "43M"
|
||||||
|
|
||||||
jsonKeyTest "50M" "POST" "/rpc/leak?columns=blob" "171M"
|
jsonKeyTest "50M" "POST" "/rpc/leak?columns=blob" "171M"
|
||||||
jsonKeyTest "50M" "POST" "/leak?columns=blob" "171M"
|
jsonKeyTest "50M" "POST" "/leak?columns=blob" "171M"
|
||||||
jsonKeyTest "50M" "PATCH" "/leak?id=eq.1&columns=blob" "171M"
|
jsonKeyTest "50M" "PATCH" "/leak?id=eq.1&columns=blob" "171M"
|
||||||
|
|
||||||
postJsonArrayTest "1000" "/perf_articles?columns=id,body" "11M"
|
postJsonArrayTest "1000" "/perf_articles?columns=id,body" "13M"
|
||||||
postJsonArrayTest "10000" "/perf_articles?columns=id,body" "11M"
|
postJsonArrayTest "10000" "/perf_articles?columns=id,body" "13M"
|
||||||
postJsonArrayTest "100000" "/perf_articles?columns=id,body" "21M"
|
postJsonArrayTest "100000" "/perf_articles?columns=id,body" "23M"
|
||||||
|
|
||||||
trap - int term exit
|
trap - int term exit
|
||||||
|
|
||||||
|
|||||||
@@ -523,7 +523,7 @@ spec actualPgVersion = describe "OpenAPI" $ do
|
|||||||
|
|
||||||
types `shouldBe` Just [aesonQQ|
|
types `shouldBe` Just [aesonQQ|
|
||||||
{
|
{
|
||||||
"format": "test.enum_menagerie_type",
|
"format": "enum_menagerie_type",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"foo",
|
"foo",
|
||||||
|
|||||||
Reference in New Issue
Block a user