Allow multiple schemas to be exposed in one instance (#1450)
The schema to use can be selected through the headers `Accept-Profile` for GET/HEAD and `Content-Profile` for POST/PATCH/PUT/DELETE. This is based on the https://www.w3.org/TR/dx-prof-conneg/ttps://www.w3.org/TR/dx-prof-conneg/ spec. Also increase all memory tests by 1M(otherwise CI fails). Co-authored-by: Mahmoud Kassem <MKassem@gk-software.com> Co-authored-by: Mahmoud Kassem <mahmoud_k@mail.com>
This commit is contained in:
co-authored by
Mahmoud Kassem
Mahmoud Kassem
parent
a80eb2ff0e
commit
691bb5640d
@@ -44,15 +44,15 @@ import PostgREST.Private.Common
|
||||
import PostgREST.Types
|
||||
import Protolude
|
||||
|
||||
getDbStructure :: Schema -> PgVersion -> HT.Transaction DbStructure
|
||||
getDbStructure schema pgVer = do
|
||||
HT.sql "set local schema ''" -- for getting the fully qualified name(schema.name) of every db object
|
||||
getDbStructure :: [Schema] -> PgVersion -> HT.Transaction DbStructure
|
||||
getDbStructure schemas pgVer = do
|
||||
HT.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
|
||||
tabs <- HT.statement () allTables
|
||||
cols <- HT.statement schema $ allColumns tabs
|
||||
srcCols <- HT.statement schema $ allSourceColumns cols pgVer
|
||||
cols <- HT.statement schemas $ allColumns tabs
|
||||
srcCols <- HT.statement schemas $ allSourceColumns cols pgVer
|
||||
m2oRels <- HT.statement () $ allM2ORels tabs cols
|
||||
keys <- HT.statement () $ allPrimaryKeys tabs
|
||||
procs <- HT.statement schema allProcs
|
||||
procs <- HT.statement schemas allProcs
|
||||
|
||||
let rels = addM2MRels . addO2MRels $ addViewM2ORels srcCols m2oRels
|
||||
cols' = addForeignKeys rels cols
|
||||
@@ -126,13 +126,14 @@ sourceColumnFromRow allCols (s1,t1,c1,s2,t2,c2) = (,) <$> col1 <*> col2
|
||||
col2 = findCol s2 t2 c2
|
||||
findCol s t c = find (\col -> (tableSchema . colTable) col == s && (tableName . colTable) col == t && colName col == c) allCols
|
||||
|
||||
decodeProcs :: HD.Result (M.HashMap Text [ProcDescription])
|
||||
decodeProcs :: HD.Result ProcsMap
|
||||
decodeProcs =
|
||||
-- Duplicate rows for a function means they're overloaded, order these by least args according to ProcDescription Ord instance
|
||||
map sort . M.fromListWith (++) . map ((\(x,y) -> (x, [y])) . addName) <$> HD.rowList tblRow
|
||||
map sort . M.fromListWith (++) . map ((\(x,y) -> (x, [y])) . addKey) <$> HD.rowList procRow
|
||||
where
|
||||
tblRow = ProcDescription
|
||||
procRow = ProcDescription
|
||||
<$> column HD.text
|
||||
<*> column HD.text
|
||||
<*> nullableColumn HD.text
|
||||
<*> (parseArgs <$> column HD.text)
|
||||
<*> (parseRetType
|
||||
@@ -142,8 +143,8 @@ decodeProcs =
|
||||
<*> column HD.char)
|
||||
<*> (parseVolatility <$> column HD.char)
|
||||
|
||||
addName :: ProcDescription -> (Text, ProcDescription)
|
||||
addName pd = (pdName pd, pd)
|
||||
addKey :: ProcDescription -> (QualifiedIdentifier, ProcDescription)
|
||||
addKey pd = (QualifiedIdentifier (pdSchema pd) (pdName pd), pd)
|
||||
|
||||
parseArgs :: Text -> [PgArg]
|
||||
parseArgs = mapMaybe parseArg . filter (not . isPrefixOf "OUT" . toS) . map strip . split (==',')
|
||||
@@ -176,31 +177,34 @@ decodeProcs =
|
||||
| v == 's' = Stable
|
||||
| otherwise = Volatile -- only 'v' can happen here
|
||||
|
||||
allProcs :: H.Statement Schema (M.HashMap Text [ProcDescription])
|
||||
allProcs = H.Statement (toS procsSqlQuery) (param HE.text) decodeProcs True
|
||||
allProcs :: H.Statement [Schema] ProcsMap
|
||||
allProcs = H.Statement (toS sql) (arrayParam HE.text) decodeProcs True
|
||||
where
|
||||
sql = procsSqlQuery <> " WHERE pn.nspname = ANY($1)"
|
||||
|
||||
accessibleProcs :: H.Statement Schema (M.HashMap Text [ProcDescription])
|
||||
accessibleProcs :: H.Statement Schema ProcsMap
|
||||
accessibleProcs = H.Statement (toS sql) (param HE.text) decodeProcs True
|
||||
where
|
||||
sql = procsSqlQuery <> " AND has_function_privilege(p.oid, 'execute')"
|
||||
sql = procsSqlQuery <> " WHERE pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')"
|
||||
|
||||
procsSqlQuery :: SqlQuery
|
||||
procsSqlQuery = [q|
|
||||
SELECT p.proname as "proc_name",
|
||||
d.description as "proc_description",
|
||||
pg_get_function_arguments(p.oid) as "args",
|
||||
tn.nspname as "rettype_schema",
|
||||
coalesce(comp.relname, t.typname) as "rettype_name",
|
||||
p.proretset as "rettype_is_setof",
|
||||
t.typtype as "rettype_typ",
|
||||
p.provolatile
|
||||
SELECT
|
||||
pn.nspname as "proc_schema",
|
||||
p.proname as "proc_name",
|
||||
d.description as "proc_description",
|
||||
pg_get_function_arguments(p.oid) as "args",
|
||||
tn.nspname as "rettype_schema",
|
||||
coalesce(comp.relname, t.typname) as "rettype_name",
|
||||
p.proretset as "rettype_is_setof",
|
||||
t.typtype as "rettype_typ",
|
||||
p.provolatile
|
||||
FROM pg_proc p
|
||||
JOIN pg_namespace pn ON pn.oid = p.pronamespace
|
||||
JOIN pg_type t ON t.oid = p.prorettype
|
||||
JOIN pg_namespace tn ON tn.oid = t.typnamespace
|
||||
LEFT JOIN pg_class comp ON comp.oid = t.typrelid
|
||||
LEFT JOIN pg_catalog.pg_description as d on d.objoid = p.oid
|
||||
WHERE pn.nspname = $1
|
||||
|]
|
||||
|
||||
schemaDescription :: H.Statement Schema (Maybe Text)
|
||||
@@ -384,9 +388,9 @@ allTables =
|
||||
GROUP BY table_schema, table_name, insertable
|
||||
ORDER BY table_schema, table_name |]
|
||||
|
||||
allColumns :: [Table] -> H.Statement Schema [Column]
|
||||
allColumns :: [Table] -> H.Statement [Schema] [Column]
|
||||
allColumns tabs =
|
||||
H.Statement sql (param HE.text) (decodeColumns tabs) True
|
||||
H.Statement sql (arrayParam HE.text) (decodeColumns tabs) True
|
||||
where
|
||||
sql = [q|
|
||||
SELECT DISTINCT
|
||||
@@ -424,7 +428,7 @@ allColumns tabs =
|
||||
AND c.relkind IN ('r', 'v', 'f', 'm')
|
||||
AND r.conrelid = c.oid
|
||||
AND c.relnamespace = n.oid
|
||||
AND n.nspname NOT IN ('pg_catalog', 'information_schema', $1)
|
||||
AND n.nspname <> ANY (ARRAY['pg_catalog', 'information_schema'] || $1)
|
||||
),
|
||||
/*
|
||||
-- CTE based on information_schema.columns
|
||||
@@ -526,7 +530,7 @@ allColumns tabs =
|
||||
AND a.attnum > 0
|
||||
AND NOT a.attisdropped
|
||||
AND (c.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'f'::"char", 'm'::"char"]))
|
||||
AND (nc.nspname = $1 OR kc.r_oid IS NOT NULL) /*--filter only columns that are FK/PK or in the api schema */
|
||||
AND (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL) /*--filter only columns that are FK/PK or in the api schema */
|
||||
/*--AND (pg_has_role(c.relowner, 'USAGE'::text) OR has_column_privilege(c.oid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text))*/
|
||||
)
|
||||
SELECT
|
||||
@@ -719,9 +723,9 @@ pkFromRow :: [Table] -> (Schema, Text, Text) -> Maybe PrimaryKey
|
||||
pkFromRow tabs (s, t, n) = PrimaryKey <$> table <*> pure n
|
||||
where table = find (\tbl -> tableSchema tbl == s && tableName tbl == t) tabs
|
||||
|
||||
allSourceColumns :: [Column] -> PgVersion -> H.Statement Schema [SourceColumn]
|
||||
allSourceColumns :: [Column] -> PgVersion -> H.Statement [Schema] [SourceColumn]
|
||||
allSourceColumns cols pgVer =
|
||||
H.Statement sql (param HE.text) (decodeSourceColumns cols) True
|
||||
H.Statement sql (arrayParam HE.text) (decodeSourceColumns cols) True
|
||||
-- query explanation at https://gist.github.com/steve-chavez/7ee0e6590cddafb532e5f00c46275569
|
||||
where
|
||||
subselectRegex :: Text
|
||||
@@ -740,7 +744,7 @@ allSourceColumns cols pgVer =
|
||||
from pg_class c
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
join pg_rewrite r on r.ev_class = c.oid
|
||||
where (c.relkind in ('v', 'm')) and n.nspname = $1
|
||||
where (c.relkind in ('v', 'm')) and n.nspname = ANY ($1)
|
||||
),
|
||||
removed_subselects as(
|
||||
select
|
||||
|
||||
Reference in New Issue
Block a user