diff --git a/postgrest.cabal b/postgrest.cabal index 83eff1d4e..9db15172a 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -70,7 +70,7 @@ executable postgrest , PostgREST.Middleware , PostgREST.Parsers , PostgREST.PgQuery - , PostgREST.PgStructure + , PostgREST.DbStructure , PostgREST.QueryBuilder , PostgREST.RangeQuery , PostgREST.Types @@ -134,7 +134,7 @@ library , PostgREST.Middleware , PostgREST.Parsers , PostgREST.PgQuery - , PostgREST.PgStructure + , PostgREST.DbStructure , PostgREST.QueryBuilder , PostgREST.RangeQuery , PostgREST.Types @@ -165,7 +165,7 @@ Test-Suite spec , PostgREST.Middleware , PostgREST.Parsers , PostgREST.PgQuery - , PostgREST.PgStructure + , PostgREST.DbStructure , PostgREST.QueryBuilder , PostgREST.RangeQuery , PostgREST.Types diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 2f1bf4b67..2779c016d 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -46,7 +46,7 @@ import qualified Hasql.Postgres as P import PostgREST.Config (AppConfig (..)) import PostgREST.Parsers import PostgREST.PgQuery -import PostgREST.PgStructure +import PostgREST.DbStructure import PostgREST.QueryBuilder import PostgREST.RangeQuery import PostgREST.Types @@ -55,7 +55,7 @@ import PostgREST.Auth (tokenJWT) import Prelude app :: DbStructure -> AppConfig -> BL.ByteString -> Request -> H.Tx P.Postgres s Response -app dbstructure conf reqBody req = +app db conf reqBody req = case (path, verb) of ([table], "GET") -> @@ -160,9 +160,9 @@ app dbstructure conf reqBody req = return $ responseLBS status404 [] "" where - allRels = relations dbstructure - allCols = columns dbstructure - allPrKeys = primaryKeys dbstructure + allRels = relations db + allCols = columns db + allPrKeys = primaryKeys db filterCol sc table (Column{colSchema=s, colTable=t}) = s==sc && table==t filterCol _ _ _ = False filterPk sc table pk = sc == pkSchema pk && table == pkTable pk @@ -332,7 +332,7 @@ addFilter (path, flt) (Node rn forest) = where maybeNode = find ((name==).fst.snd.rootLabel) forst toSourceRelation :: Text -> Relation -> Maybe Relation -toSourceRelation mt r@(Relation _ t _ ft _ _ rt _ _) +toSourceRelation mt r@(Relation _ t _ _ ft _ _ _ rt _ _) | mt == t = Just $ r {relTable=sourceSubqueryName} | mt == ft = Just $ r {relFTable=sourceSubqueryName} | Just mt == rt = Just $ r {relLTable=Just sourceSubqueryName} diff --git a/src/PostgREST/PgStructure.hs b/src/PostgREST/DbStructure.hs similarity index 85% rename from src/PostgREST/PgStructure.hs rename to src/PostgREST/DbStructure.hs index 74d620078..911856202 100644 --- a/src/PostgREST/PgStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -3,7 +3,7 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeSynonymInstances #-} -module PostgREST.PgStructure where +module PostgREST.DbStructure where import Control.Applicative import Control.Monad (join) @@ -21,6 +21,20 @@ import PostgREST.Types import GHC.Exts (groupWith) import Prelude +createDbStructure :: H.Tx P.Postgres s DbStructure +createDbStructure = do + tabs <- allTables + rels <- allRelations + cols <- allColumns rels + keys <- allPrimaryKeys + + return DbStructure { + tables = tabs + , columns = cols + , relations = rels + , primaryKeys = keys + } + doesProc :: forall c s. B.CxValue c Int => (Text -> Text -> B.Stmt c) -> Text -> Text -> H.Tx c s Bool doesProc stmt schema proc = do @@ -64,15 +78,15 @@ columnFromRow (s, t, n, pos, nul, typ, u, l, p, d, e) = parseEnum str = fromMaybe [] $ split (==',') <$> str -relationFromRow :: (Text, Text, [Text], Text, [Text]) -> Relation -relationFromRow (s, t, cs, ft, fcs) = Relation s t cs ft fcs Child Nothing Nothing Nothing +relationFromRow :: (Text, Text, [Text], Text, Text, [Text]) -> Relation +relationFromRow (s, t, cs, fs, ft, fcs) = Relation s t cs fs ft fcs Child Nothing Nothing Nothing Nothing pkFromRow :: (Text, Text, Text) -> PrimaryKey pkFromRow (s, t, n) = PrimaryKey s t n addParentRelation :: Relation -> [Relation] -> [Relation] -addParentRelation rel@(Relation s t c ft fc _ _ _ _) rels = Relation s ft fc t c Parent Nothing Nothing Nothing:rel:rels +addParentRelation rel@(Relation s t c fs ft fc _ _ _ _ _) rels = Relation fs ft fc s t c Parent Nothing Nothing Nothing Nothing:rel:rels -- allTables :: H.Tx P.Postgres s [Table] -- allTables = do @@ -135,9 +149,10 @@ allRelations :: H.Tx P.Postgres s [Relation] allRelations = do rels <- H.listEx $ [H.stmt| WITH table_fk AS ( - SELECT ns.nspname AS table_schema, + SELECT ns1.nspname AS table_schema, tab.relname AS table_name, column_info.cols AS columns, + ns2.nspname AS foreign_table_schema, other.relname AS foreign_table_name, column_info.refs AS foreign_columns FROM pg_constraint, @@ -152,10 +167,10 @@ allRelations = do WHERE attrelid = confrelid AND attnum = ref) AS refs) AS column_info, - LATERAL (SELECT * FROM pg_namespace - WHERE pg_namespace.oid = connamespace) AS ns, + LATERAL (SELECT * FROM pg_namespace WHERE pg_namespace.oid = connamespace) AS ns1, LATERAL (SELECT * FROM pg_class WHERE pg_class.oid = conrelid) AS tab, - LATERAL (SELECT * FROM pg_class WHERE pg_class.oid = confrelid) AS other + LATERAL (SELECT * FROM pg_class WHERE pg_class.oid = confrelid) AS other, + LATERAL (SELECT * FROM pg_namespace WHERE pg_namespace.oid = other.relnamespace) AS ns2 WHERE confrelid != 0 ORDER BY (conrelid, column_info.nums) ) @@ -167,33 +182,35 @@ allRelations = do vcu.table_schema, vcu.view_name AS table_name, array_agg(vcu.column_name::text) AS columns, + table_fk.foreign_table_schema, table_fk.foreign_table_name, table_fk.foreign_columns - FROM information_schema.view_column_usage as vcu + 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 vcu.column_name = ANY (table_fk.columns) WHERE vcu.view_schema NOT IN ('pg_catalog', 'information_schema') AND columns = table_fk.columns - GROUP BY vcu.table_schema, vcu.view_name, table_fk.foreign_table_name, table_fk.foreign_columns + GROUP BY vcu.table_schema, vcu.view_name, table_fk.foreign_table_schema, table_fk.foreign_table_name, table_fk.foreign_columns ) UNION ( SELECT - vcu.view_schema as table_schema, + table_fk.table_schema, table_fk.table_name, table_fk.columns, - vcu.view_name as foreign_table_name, - array_agg(vcu.column_name::text) as foreign_columns - FROM information_schema.view_column_usage as vcu + vcu.view_schema AS foreign_table_schema, + vcu.view_name AS foreign_table_name, + array_agg(vcu.column_name::text) AS foreign_columns + FROM information_schema.view_column_usage AS vcu JOIN table_fk ON table_fk.table_schema = vcu.view_schema AND table_fk.foreign_table_name = vcu.table_name AND vcu.column_name = ANY (table_fk.foreign_columns) WHERE vcu.view_schema NOT IN ('pg_catalog', 'information_schema') AND foreign_columns = table_fk.foreign_columns - GROUP BY vcu.view_schema, table_fk.table_name, vcu.view_name, table_fk.columns + GROUP BY table_fk.table_schema, table_fk.table_name, vcu.view_schema, vcu.view_name, table_fk.columns ) |] let simpleRelations = foldr (addParentRelation.relationFromRow) [] rels @@ -204,10 +221,10 @@ allRelations = do groupFn (Relation{relSchema=s, relTable=t}) = s<>"_"<>t combinations k ns = filter ((k==).length) (subsequences ns) link2Relation [ - Relation{relSchema=sc, relTable=lt, relColumns=lc1, relFTable=t, relFColumns=c}, - Relation{ relColumns=lc2, relFTable=ft, relFColumns=fc} + Relation{relSchema=ls, relTable=lt, relColumns=lc1, relFSchema=s, relFTable=t, relFColumns=c}, + Relation{ relColumns=lc2, relFSchema=fs, relFTable=ft, relFColumns=fc} ] - | lc1 /= lc2 && length lc1 == 1 && length lc2 == 1 = Just $ Relation sc t c ft fc Many (Just lt) (Just lc1) (Just lc2) + | lc1 /= lc2 && length lc1 == 1 && length lc2 == 1 = Just $ Relation s t c fs ft fc Many (Just ls) (Just lt) (Just lc1) (Just lc2) | otherwise = Nothing link2Relation _ = Nothing @@ -264,7 +281,7 @@ allColumns rels = do lookupFn (Column{colSchema=cs, colTable=ct, colName=cn}) (Relation{relSchema=rs, relTable=rt, relColumns=rc, relType=rty}) = cs==rs && ct==rt && cn `elem` rc && rty==Child lookupFn _ _ = False - relToFk cName (Relation{relFTable=t, relColumns=cs, relFColumns=fcs}) = ForeignKey t <$> c + relToFk cName (Relation{relSchema=s, relFTable=t, relColumns=cs, relFColumns=fcs}) = ForeignKey s t <$> c where pos = elemIndex cName cs c = (fcs !!) <$> pos diff --git a/src/PostgREST/Main.hs b/src/PostgREST/Main.hs index 311e895cc..53cf2b917 100644 --- a/src/PostgREST/Main.hs +++ b/src/PostgREST/Main.hs @@ -2,14 +2,13 @@ module Main where import PostgREST.App --- import PostgREST.QueryBuilder import PostgREST.Config (AppConfig (..), minimumPgVersion, prettyVersion, readOptions) import PostgREST.Error (errResponse, PgError) import PostgREST.Middleware -import PostgREST.PgStructure +import PostgREST.DbStructure import PostgREST.Types import Control.Monad (unless) @@ -27,7 +26,6 @@ import Network.Wai.Middleware.RequestLogger (logStdout) import System.IO (BufferMode (..), hSetBuffering, stderr, stdin, stdout) --- import Data.Maybe (mapMaybe) isServerVersionSupported :: H.Session P.Postgres IO Bool isServerVersionSupported = do @@ -70,38 +68,12 @@ main = do <> show minimumPgVersion) ) supportedOrError - -- what was this code for? - -- roleOrError <- H.session pool $ do - -- Identity (role :: Text) <- H.tx Nothing $ H.singleEx - -- [H.stmt|SELECT SESSION_USER|] - -- return role - -- authenticator <- either hasqlError return roleOrError - let txSettings = Just (H.ReadCommitted, Just True) - metadata <- H.session pool $ H.tx txSettings $ do - rels <- allRelations - cols <- allColumns rels - keys <- allPrimaryKeys - return (rels, cols, keys) - - - dbstructure <- either hasqlError - (\(rels, cols, keys) -> - - return DbStructure { - columns=cols - , relations=rels - , primaryKeys=keys - } - ) metadata - - -- let allRels = relations dbstructure - -- fakeRels = mapMaybe (toSourceRelation "projects") allRels - -- - -- print $ findRelation (fakeRels ++ allRels) "test" "pg_source" "clients" + dbOrError <- H.session pool $ H.tx txSettings createDbStructure + db <- either hasqlError return dbOrError runSettings appSettings $ middle $ \ req respond -> do body <- strictRequestBody req resOrError <- liftIO $ H.session pool $ H.tx txSettings $ - runWithClaims conf (app dbstructure conf body) req + runWithClaims conf (app db conf body) req either (respond . errResponse) respond resOrError diff --git a/src/PostgREST/MainTest.hs b/src/PostgREST/MainTest.hs index 55ebec5bc..45326d33d 100644 --- a/src/PostgREST/MainTest.hs +++ b/src/PostgREST/MainTest.hs @@ -8,7 +8,7 @@ import PostgREST.Config (AppConfig (..), readOptions) import PostgREST.Error (errResponse, PgError) import PostgREST.Middleware -import PostgREST.PgStructure +import PostgREST.DbStructure import PostgREST.Types import Control.Monad (unless) @@ -94,7 +94,7 @@ main = do return (tabs, rels, cols, keys) - dbstructure <- either hasqlError + db <- either hasqlError (\(tabs, rels, cols, keys) -> return DbStructure { @@ -107,10 +107,10 @@ main = do runSettings appSettings $ middle $ \ req respond -> do body <- strictRequestBody req resOrError <- liftIO $ H.session pool $ H.tx txSettings $ - runWithClaims conf (app dbstructure conf body) req + runWithClaims conf (app db conf body) req either (respond . errResponse) respond resOrError - --let allRels = relations dbstructure + --let allRels = relations db -- links = join $ map (combinations 2) $ filter ((>=1).length) $ groupWith groupFn $ filter ( (==Child). relType) allRels -- combinations k ns = filter ((k==).length) (subsequences ns) diff --git a/src/PostgREST/PgQuery.hs b/src/PostgREST/PgQuery.hs index 9d66f6ecb..62cf96df7 100644 --- a/src/PostgREST/PgQuery.hs +++ b/src/PostgREST/PgQuery.hs @@ -281,7 +281,7 @@ pgFmtCondition table (Filter (col,jp) ops val) = _ -> "" valToStr v = case v of VText s -> pgFmtValue opCode s - VForeignKey (QualifiedIdentifier s _) (ForeignKey ft fc) -> pgFmtColumn qi fc + VForeignKey (QualifiedIdentifier s _) (ForeignKey _ ft fc) -> pgFmtColumn qi fc where qi = QualifiedIdentifier (if ft == sourceSubqueryName then "" else s) ft pgFmtColumn :: QualifiedIdentifier -> T.Text -> T.Text diff --git a/src/PostgREST/QueryBuilder.hs b/src/PostgREST/QueryBuilder.hs index cd04960fa..27799ac99 100644 --- a/src/PostgREST/QueryBuilder.hs +++ b/src/PostgREST/QueryBuilder.hs @@ -35,14 +35,14 @@ addRelations schema allRelations parentNode node@(Node n@(query, (table, _)) for updatedForest = mapM (addRelations schema allRelations (Just node)) forest getJoinConditions :: Relation -> [Filter] -getJoinConditions (Relation s t cs ft fcs typ lt lc1 lc2) = +getJoinConditions (Relation s t cs fs ft fcs typ ls lt lc1 lc2) = case typ of - Child -> zipWith (toFilter t ft) cs fcs - Parent -> zipWith (toFilter t ft) cs fcs - Many -> zipWith (toFilter t (fromMaybe "" lt)) cs (fromMaybe [] lc1) ++ zipWith (toFilter ft (fromMaybe "" lt)) fcs (fromMaybe [] lc2) + Child -> zipWith (toFilter t fs ft) cs fcs + Parent -> zipWith (toFilter t fs ft) cs fcs + Many -> zipWith (toFilter t (fromMaybe "" ls) (fromMaybe "" lt)) cs (fromMaybe [] lc1) ++ zipWith (toFilter ft (fromMaybe "" ls) (fromMaybe "" lt)) fcs (fromMaybe [] lc2) where - toFilter :: Text -> Text -> FieldName -> FieldName -> Filter - toFilter tb ftb c fc = Filter (c, Nothing) "=" (VForeignKey (QualifiedIdentifier s tb) (ForeignKey ftb fc)) + toFilter :: Text -> Text -> Text -> FieldName -> FieldName -> Filter + toFilter tb fsc ftb c fc = Filter (c, Nothing) "=" (VForeignKey (QualifiedIdentifier s tb) (ForeignKey fsc ftb fc)) addJoinConditions :: Text -> ApiRequest -> Either Text ApiRequest addJoinConditions schema (Node (query, (t, r)) forest) = @@ -55,7 +55,7 @@ addJoinConditions schema (Node (query, (t, r)) forest) = where q = addCond updatedQuery (getJoinConditions rel) qq = q{from=linkTable:from q} - _ -> Left "unknow relation" + _ -> Left "unknown relation" where -- add parentTable and parentJoinConditions to the query updatedQuery = foldr (flip addCond) (query{from = parentTables ++ from query}) parentJoinConditions diff --git a/src/PostgREST/Types.hs b/src/PostgREST/Types.hs index 691efad99..a65835473 100644 --- a/src/PostgREST/Types.hs +++ b/src/PostgREST/Types.hs @@ -19,7 +19,9 @@ data Table = Table { } deriving (Show) data ForeignKey = ForeignKey { - fkTable::Text, fkCol::Text + fkSchema :: Text, + fkTable :: Text, + fkCol :: Text } deriving (Show, Eq) @@ -36,7 +38,7 @@ data Column = Column { , colDefault :: Maybe Text , colEnum :: [Text] , colFK :: Maybe ForeignKey -} | Star {colSchema :: Text, colTable :: Text } deriving (Show) +} | Star { colSchema :: Text, colTable :: Text } deriving (Show) data PrimaryKey = PrimaryKey { pkSchema::Text, pkTable::Text, pkName::Text @@ -56,13 +58,15 @@ data QualifiedIdentifier = QualifiedIdentifier { data RelationType = Child | Parent | Many deriving (Show, Eq) data Relation = Relation { - relSchema :: Text -, relTable :: Text + relSchema :: Text +, relTable :: Text , relColumns :: [Text] -, relFTable :: Text +, relFSchema :: Text +, relFTable :: Text , relFColumns :: [Text] -, relType :: RelationType -, relLTable :: Maybe Text +, relType :: RelationType +, relLSchema :: Maybe Text +, relLTable :: Maybe Text , relLCols1 :: Maybe [Text] , relLCols2 :: Maybe [Text] } deriving (Show, Eq) @@ -101,7 +105,7 @@ instance ToJSON Column where , "enum" .= colEnum c ] instance ToJSON ForeignKey where - toJSON fk = object ["table".=fkTable fk, "column".=fkCol fk] + toJSON fk = object ["schema".=fkSchema fk, "table".=fkTable fk, "column".=fkCol fk] instance ToJSON Table where toJSON v = object [ diff --git a/test/Feature/StructureSpec.hs b/test/Feature/StructureSpec.hs index 2a28f43db..148a1ca1b 100644 --- a/test/Feature/StructureSpec.hs +++ b/test/Feature/StructureSpec.hs @@ -175,6 +175,7 @@ spec = around withApp $ do }, { "references":{ + "schema":"test", "column":"id", "table":"auto_incrementing_pk" }, @@ -191,6 +192,7 @@ spec = around withApp $ do }, { "references":{ + "schema":"test", "column":"k", "table":"simple_pk" }, diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs index 45cbcbe96..dcc765f0d 100644 --- a/test/SpecHelper.hs +++ b/test/SpecHelper.hs @@ -30,8 +30,7 @@ import PostgREST.App (app) import PostgREST.Config (AppConfig(..)) import PostgREST.Middleware import PostgREST.Error(errResponse) -import PostgREST.PgStructure -import PostgREST.Types +import PostgREST.DbStructure dbString :: String dbString = "postgres://postgrest_test@localhost:5432/postgrest_test" @@ -55,25 +54,13 @@ withApp perform = do <- H.acquirePool pgSettings testPoolOpts let txSettings = Just (H.ReadCommitted, Just True) - metadata <- H.session pool $ H.tx txSettings $ do - rels <- allRelations - cols <- allColumns rels - keys <- allPrimaryKeys - return (rels, cols, keys) - - dbstructure <- case metadata of - Left e -> fail $ show e - Right (rels, cols, keys) -> - return DbStructure { - columns=cols - , relations=rels - , primaryKeys=keys - } + dbOrError <- H.session pool $ H.tx txSettings createDbStructure + db <- either (fail . show) return dbOrError perform $ middle $ \req resp -> do body <- strictRequestBody req result <- liftIO $ H.session pool $ H.tx txSettings - $ runWithClaims cfg (app dbstructure cfg body) req + $ runWithClaims cfg (app db cfg body) req either (resp . errResponse) resp result where middle = defaultMiddle diff --git a/test/Unit/PgStructureSpec.hx b/test/Unit/DbStructureSpec.hx similarity index 93% rename from test/Unit/PgStructureSpec.hx rename to test/Unit/DbStructureSpec.hx index b1c1570dd..6a8a9e4de 100644 --- a/test/Unit/PgStructureSpec.hx +++ b/test/Unit/DbStructureSpec.hx @@ -1,7 +1,7 @@ -module Unit.PgStructureSpec where +module Unit.DbStructureSpec where import Test.Hspec -import PgStructure (Table(..), tables, Column(..), columns, ForeignKey(..), +import DbStructure (Table(..), tables, Column(..), columns, ForeignKey(..), foreignKeys) import Database.HDBC (quickQuery)