314 lines
12 KiB
Haskell
314 lines
12 KiB
Haskell
{-# LANGUAGE QuasiQuotes, OverloadedStrings, TypeSynonymInstances,
|
|
MultiParamTypeClasses, ScopedTypeVariables,
|
|
FlexibleContexts #-}
|
|
module PostgREST.PgStructure where
|
|
|
|
import PostgREST.PgQuery (QualifiedIdentifier(..))
|
|
import PostgREST.Types
|
|
import Data.Text (Text, unpack, split)
|
|
import Data.List (find)
|
|
import Data.Aeson
|
|
import Data.Functor.Identity
|
|
import Data.String.Conversions (cs)
|
|
import Data.Maybe (fromMaybe, isJust)
|
|
import Control.Applicative
|
|
|
|
import qualified Data.Map as Map
|
|
|
|
import qualified Hasql as H
|
|
import qualified Hasql.Postgres as P
|
|
|
|
import Prelude
|
|
|
|
-----------
|
|
-- foreignKeys :: QualifiedIdentifier -> H.Tx P.Postgres s (Map.Map Text ForeignKey)
|
|
-- foreignKeys table = do
|
|
-- r <- H.listEx $ [H.stmt|
|
|
-- select kcu.column_name, ccu.table_name AS foreign_table_name,
|
|
-- ccu.column_name AS foreign_column_name
|
|
-- from information_schema.table_constraints AS tc
|
|
-- join information_schema.key_column_usage AS kcu
|
|
-- on tc.constraint_name = kcu.constraint_name
|
|
-- join information_schema.constraint_column_usage AS ccu
|
|
-- on ccu.constraint_name = tc.constraint_name
|
|
-- where constraint_type = 'FOREIGN KEY'
|
|
-- and tc.table_name=? and tc.table_schema = ?
|
|
-- order by kcu.column_name
|
|
-- |] (qiName table) (qiSchema table)
|
|
--
|
|
-- return $ foldl addKey Map.empty r
|
|
-- where
|
|
-- addKey :: Map.Map Text ForeignKey -> (Text, Text, Text) -> Map.Map Text ForeignKey
|
|
-- addKey m (col, ftab, fcol) = Map.insert col (ForeignKey ftab fcol) m
|
|
--
|
|
--
|
|
-- tables :: Text -> H.Tx P.Postgres s [Table]
|
|
-- tables schema = do
|
|
-- rows <- H.listEx $
|
|
-- [H.stmt|
|
|
-- select
|
|
-- n.nspname as table_schema,
|
|
-- relname as table_name,
|
|
-- c.relkind = 'r' or (c.relkind IN ('v', 'f')) and (pg_relation_is_updatable(c.oid::regclass, false) & 8) = 8
|
|
-- or (exists (
|
|
-- select 1
|
|
-- from pg_trigger
|
|
-- where pg_trigger.tgrelid = c.oid and (pg_trigger.tgtype::integer & 69) = 69)
|
|
-- ) as insertable
|
|
-- from
|
|
-- pg_class c
|
|
-- join pg_namespace n on n.oid = c.relnamespace
|
|
-- where
|
|
-- c.relkind in ('v', 'r', 'm')
|
|
-- and n.nspname = ?
|
|
-- and (
|
|
-- pg_has_role(c.relowner, 'USAGE'::text)
|
|
-- or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text)
|
|
-- or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text)
|
|
-- )
|
|
-- order by relname
|
|
-- |] schema
|
|
-- return $ map tableFromRow rows
|
|
|
|
|
|
-- columns :: QualifiedIdentifier -> H.Tx P.Postgres s [Column]
|
|
-- columns table = do
|
|
-- cols <- H.listEx $ [H.stmt|
|
|
-- select info.table_schema as schema, info.table_name as table_name,
|
|
-- info.column_name as name, info.ordinal_position as position,
|
|
-- info.is_nullable::boolean as nullable, info.data_type as col_type,
|
|
-- info.is_updatable::boolean as updatable,
|
|
-- info.character_maximum_length as max_len,
|
|
-- info.numeric_precision as precision,
|
|
-- info.column_default as default_value,
|
|
-- array_to_string(enum_info.vals, ',') as enum
|
|
-- from (
|
|
-- select table_schema, table_name, column_name, ordinal_position,
|
|
-- is_nullable, data_type, is_updatable,
|
|
-- character_maximum_length, numeric_precision,
|
|
-- column_default, udt_name
|
|
-- from information_schema.columns
|
|
-- where table_schema = ? and table_name = ?
|
|
-- ) as 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_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
-- group by s, n
|
|
-- ) as enum_info
|
|
-- on (info.udt_name = enum_info.n)
|
|
-- order by position |]
|
|
-- (qiSchema table) (qiName table)
|
|
--
|
|
-- fks <- foreignKeys table
|
|
-- return $ map (addFK fks . columnFromRow) cols
|
|
--
|
|
-- where
|
|
-- addFK fks col = col { colFK = Map.lookup (cs . colName $ col) fks }
|
|
|
|
|
|
primaryKeyColumns :: QualifiedIdentifier -> H.Tx P.Postgres s [Text]
|
|
primaryKeyColumns table = do
|
|
r <- H.listEx $ [H.stmt|
|
|
select kc.column_name
|
|
from
|
|
information_schema.table_constraints tc,
|
|
information_schema.key_column_usage kc
|
|
where
|
|
tc.constraint_type = 'PRIMARY KEY'
|
|
and kc.table_name = tc.table_name and kc.table_schema = tc.table_schema
|
|
and kc.constraint_name = tc.constraint_name
|
|
and kc.table_schema = ?
|
|
and kc.table_name = ? |] (qiSchema table) (qiName table)
|
|
return $ map runIdentity r
|
|
|
|
doesProcExist :: Text -> Text -> H.Tx P.Postgres s Bool
|
|
doesProcExist schema proc = do
|
|
row :: Maybe (Identity Int) <- H.maybeEx $ [H.stmt|
|
|
SELECT 1
|
|
FROM pg_catalog.pg_namespace n
|
|
JOIN pg_catalog.pg_proc p
|
|
ON pronamespace = n.oid
|
|
WHERE nspname = ?
|
|
AND proname = ?
|
|
|] schema proc
|
|
return $ isJust row
|
|
|
|
|
|
tableFromRow :: (Text, Text, Bool) -> Table
|
|
tableFromRow (s, n, i) = Table s n i
|
|
|
|
columnFromRow :: (Text, Text, Text,
|
|
Int, Bool, Text,
|
|
Bool, Maybe Int, Maybe Int,
|
|
Maybe Text, Maybe Text)
|
|
-> Column
|
|
columnFromRow (s, t, n, pos, nul, typ, u, l, p, d, e) =
|
|
Column s t n pos nul typ u l p d (parseEnum e) Nothing
|
|
|
|
where
|
|
parseEnum :: Maybe Text -> [Text]
|
|
parseEnum str = fromMaybe [] $ split (==',') <$> str
|
|
|
|
|
|
instance ToJSON Column where
|
|
toJSON c = object [
|
|
"schema" .= colSchema c
|
|
, "name" .= colName c
|
|
, "position" .= colPosition c
|
|
, "nullable" .= colNullable c
|
|
, "type" .= colType c
|
|
, "updatable" .= colUpdatable c
|
|
, "maxLen" .= colMaxLen c
|
|
, "precision" .= colPrecision c
|
|
, "references".= colFK c
|
|
, "default" .= colDefault c
|
|
, "enum" .= colEnum c ]
|
|
|
|
instance ToJSON ForeignKey where
|
|
toJSON fk = object ["table".=fkTable fk, "column".=fkCol fk]
|
|
|
|
instance ToJSON Table where
|
|
toJSON v = object [
|
|
"schema" .= tableSchema v
|
|
, "name" .= tableName v
|
|
, "insertable" .= tableInsertable v ]
|
|
------------
|
|
|
|
|
|
relationFromRow :: (Text, Text, Text, Text, Text) -> Relation
|
|
relationFromRow (s, t, c, ft, fc) = Relation s t c ft fc "child"
|
|
|
|
pkFromRow :: (Text, Text, Text) -> PrimaryKey
|
|
pkFromRow (s, t, n) = PrimaryKey s t n
|
|
|
|
|
|
addFlippedRelation :: Relation -> [Relation] -> [Relation]
|
|
addFlippedRelation rel@(Relation s t c ft fc _) rels = Relation s ft fc t c "parent":rel:rels
|
|
|
|
alltables :: H.Tx P.Postgres s [Table]
|
|
alltables = do
|
|
rows <- H.listEx $ [H.stmt|
|
|
SELECT n.nspname AS table_schema,
|
|
relname AS TABLE_NAME,
|
|
c.relkind = 'r' OR (c.relkind IN ('v','f'))
|
|
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8
|
|
OR (EXISTS ( SELECT 1
|
|
FROM pg_trigger
|
|
WHERE pg_trigger.tgrelid = c.oid
|
|
AND (pg_trigger.tgtype::integer & 69) = 69)
|
|
) AS insertable
|
|
FROM pg_class c
|
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE c.relkind IN ('v','r','m')
|
|
AND n.nspname NOT IN ('information_schema','pg_catalog')
|
|
AND ( pg_has_role(c.relowner, 'USAGE'::text)
|
|
OR has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text)
|
|
OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES'::text)
|
|
)
|
|
ORDER BY relname
|
|
|]
|
|
return $ map tableFromRow rows
|
|
|
|
allrelations :: H.Tx P.Postgres s [Relation]
|
|
allrelations = do
|
|
rels <- H.listEx $ [H.stmt|
|
|
WITH table_fk AS (
|
|
SELECT DISTINCT
|
|
tc.table_schema, tc.table_name, kcu.column_name,
|
|
ccu.table_name AS foreign_table_name,
|
|
ccu.column_name AS foreign_column_name
|
|
FROM information_schema.table_constraints AS tc
|
|
JOIN information_schema.key_column_usage AS kcu on tc.constraint_name = kcu.constraint_name
|
|
JOIN information_schema.constraint_column_usage AS ccu on ccu.constraint_name = tc.constraint_name
|
|
WHERE constraint_type = 'FOREIGN KEY'
|
|
AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
ORDER BY tc.table_schema, tc.table_name, kcu.column_name
|
|
)
|
|
SELECT * FROM table_fk
|
|
UNION
|
|
(
|
|
SELECT DISTINCT
|
|
vcu.table_schema, vcu.view_name AS table_name, vcu.column_name,
|
|
table_fk.foreign_table_name,
|
|
table_fk.foreign_column_name
|
|
FROM information_schema.view_column_usage as vcu
|
|
JOIN table_fk ON
|
|
table_fk.table_schema = vcu.view_schema AND
|
|
table_fk.table_name = vcu.table_name AND
|
|
table_fk.column_name = vcu.column_name
|
|
WHERE vcu.view_schema NOT IN ('pg_catalog', 'information_schema')
|
|
ORDER BY vcu.table_schema, vcu.view_name, vcu.column_name
|
|
)
|
|
|
|
|]
|
|
return $ foldr (addFlippedRelation.relationFromRow) [] rels
|
|
|
|
allcolumns :: [Relation] -> H.Tx P.Postgres s [Column]
|
|
allcolumns relations = do
|
|
cols <- H.listEx $ [H.stmt|
|
|
SELECT
|
|
info.table_schema AS schema,
|
|
info.table_name AS table_name,
|
|
info.column_name AS name,
|
|
info.ordinal_position AS position,
|
|
info.is_nullable::boolean AS nullable,
|
|
info.data_type AS col_type,
|
|
info.is_updatable::boolean AS updatable,
|
|
info.character_maximum_length AS max_len,
|
|
info.numeric_precision AS precision,
|
|
info.column_default AS default_value,
|
|
array_to_string(enum_info.vals, ',') AS enum
|
|
FROM (
|
|
SELECT
|
|
table_schema,
|
|
table_name,
|
|
column_name,
|
|
ordinal_position,
|
|
is_nullable,
|
|
data_type,
|
|
is_updatable,
|
|
character_maximum_length,
|
|
numeric_precision,
|
|
column_default,
|
|
udt_name
|
|
FROM information_schema.columns
|
|
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
) AS 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_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
GROUP BY s,n
|
|
) AS enum_info ON (info.udt_name = enum_info.n)
|
|
ORDER BY schema, position
|
|
|]
|
|
return $ map (addFK . columnFromRow) cols
|
|
|
|
where
|
|
addFK col = col { colFK = relToFk <$> find (lookupFn col) relations }
|
|
lookupFn (Column{colSchema=cs, colTable=ct, colName=cn}) (Relation{relSchema=rs, relTable=rt, relColumn=rc, relType=rty}) =
|
|
cs==rs && ct==rt && cn==rc && rty=="child"
|
|
relToFk (Relation{relFTable=t, relFColumn=c}) = ForeignKey t c
|
|
|
|
allprimaryKeys :: H.Tx P.Postgres s [PrimaryKey]
|
|
allprimaryKeys = do
|
|
pks <- H.listEx $ [H.stmt|
|
|
SELECT kc.table_schema, kc.table_name, kc.column_name
|
|
FROM information_schema.table_constraints tc,
|
|
information_schema.key_column_usage kc
|
|
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
AND kc.table_name = tc.table_name
|
|
AND kc.table_schema = tc.table_schema
|
|
AND kc.constraint_name = tc.constraint_name
|
|
AND kc.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
|]
|
|
return $ map pkFromRow pks
|