refactor: Relation to Relationship

Also remove the unused UnknownRelationship error.
This commit is contained in:
steve-chavez
2021-04-21 10:28:20 -05:00
committed by Steve Chavez
parent 7ca0d46936
commit 21d280497b
9 changed files with 124 additions and 129 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ library
PostgREST.DbStructure.Identifiers PostgREST.DbStructure.Identifiers
PostgREST.DbStructure.PgVersion PostgREST.DbStructure.PgVersion
PostgREST.DbStructure.Proc PostgREST.DbStructure.Proc
PostgREST.DbStructure.Relation PostgREST.DbStructure.Relationship
PostgREST.DbStructure.Table PostgREST.DbStructure.Table
PostgREST.Error PostgREST.Error
PostgREST.GucHeader PostgREST.GucHeader
+1 -1
View File
@@ -518,7 +518,7 @@ readRequest :: Monad m => QualifiedIdentifier -> RequestContext -> Handler m Rea
readRequest QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure apiRequest _) = readRequest QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure apiRequest _) =
liftEither $ liftEither $
ReqBuilder.readRequest qiSchema qiName configDbMaxRows ReqBuilder.readRequest qiSchema qiName configDbMaxRows
(dbRelations dbStructure) (dbRelationships dbStructure)
apiRequest apiRequest
contentTypeHeaders :: RequestContext -> [HTTP.Header] contentTypeHeaders :: RequestContext -> [HTTP.Header]
+32 -32
View File
@@ -50,11 +50,11 @@ import PostgREST.DbStructure.Proc (PgArg (..), PgType (..),
ProcDescription (..), ProcDescription (..),
ProcVolatility (..), ProcVolatility (..),
ProcsMap, RetType (..)) ProcsMap, RetType (..))
import PostgREST.DbStructure.Relation (Cardinality (..), import PostgREST.DbStructure.Relationship (Cardinality (..),
ForeignKey (..), ForeignKey (..),
Junction (..), Junction (..),
PrimaryKey (..), PrimaryKey (..),
Relation (..)) Relationship (..))
import PostgREST.DbStructure.Table (Column (..), Table (..)) import PostgREST.DbStructure.Table (Column (..), Table (..))
import Protolude hiding (toS) import Protolude hiding (toS)
@@ -65,7 +65,7 @@ import Protolude.Unsafe (unsafeHead)
data DbStructure = DbStructure data DbStructure = DbStructure
{ dbTables :: [Table] { dbTables :: [Table]
, dbColumns :: [Column] , dbColumns :: [Column]
, dbRelations :: [Relation] , dbRelationships :: [Relationship]
, dbPrimaryKeys :: [PrimaryKey] , dbPrimaryKeys :: [PrimaryKey]
, dbProcs :: ProcsMap , dbProcs :: ProcsMap
, pgVersion :: PgVersion , pgVersion :: PgVersion
@@ -104,7 +104,7 @@ getDbStructure schemas extraSearchPath pgVer prepared = do
return DbStructure { return DbStructure {
dbTables = tabs dbTables = tabs
, dbColumns = cols' , dbColumns = cols'
, dbRelations = rels , dbRelationships = rels
, dbPrimaryKeys = keys' , dbPrimaryKeys = keys'
, dbProcs = procs , dbProcs = procs
, pgVersion = pgVer , pgVersion = pgVer
@@ -135,7 +135,7 @@ decodeColumns tables =
<*> nullableColumn HD.text <*> nullableColumn HD.text
<*> nullableColumn HD.text <*> nullableColumn HD.text
decodeRels :: [Table] -> [Column] -> HD.Result [Relation] decodeRels :: [Table] -> [Column] -> HD.Result [Relationship]
decodeRels tables cols = decodeRels tables cols =
mapMaybe (relFromRow tables cols) <$> HD.rowList relRow mapMaybe (relFromRow tables cols) <$> HD.rowList relRow
where where
@@ -341,28 +341,28 @@ accessibleTables =
) )
order by relname |] order by relname |]
addForeignKeys :: [Relation] -> [Column] -> [Column] addForeignKeys :: [Relationship] -> [Column] -> [Column]
addForeignKeys rels = map addFk addForeignKeys rels = map addFk
where where
addFk col = col { colFK = fk col } addFk col = col { colFK = fk col }
fk col = find (lookupFn col) rels >>= relToFk col fk col = find (lookupFn col) rels >>= relToFk col
lookupFn :: Column -> Relation -> Bool lookupFn :: Column -> Relationship -> Bool
lookupFn c rel = case rel of lookupFn c rel = case rel of
Relation{relColumns=cs, relCardinality=M2O _} -> c `elem` cs Relationship{relColumns=cs, relCardinality=M2O _} -> c `elem` cs
_ -> False _ -> False
relToFk col Relation{relColumns=cols, relForeignColumns=colsF} = do relToFk col Relationship{relColumns=cols, relForeignColumns=colsF} = do
pos <- L.elemIndex col cols pos <- L.elemIndex col cols
colF <- atMay colsF pos colF <- atMay colsF pos
return $ ForeignKey colF return $ ForeignKey colF
{- {-
Adds Views M2O Relations based on SourceColumns found, the logic is as follows: Adds Views M2O Relationships based on SourceColumns found, the logic is as follows:
Having a Relation{relTable=t1, relColumns=[c1], relFTable=t2, relFColumns=[c2], relCardinality=M2O} represented by: Having a Relationship{relTable=t1, relColumns=[c1], relFTable=t2, relFColumns=[c2], relCardinality=M2O} represented by:
t1.c1------t2.c2 t1.c1------t2.c2
When only having a t1_view.c1 source column, we need to add a View-Table M2O Relation When only having a t1_view.c1 source column, we need to add a View-Table M2O Relationship
t1.c1----t2.c2 t1.c1----------t2.c2 t1.c1----t2.c2 t1.c1----------t2.c2
-> ________/ -> ________/
@@ -370,24 +370,24 @@ When only having a t1_view.c1 source column, we need to add a View-Table M2O Rel
t1_view.c1 t1_view.c1 t1_view.c1 t1_view.c1
When only having a t2_view.c2 source column, we need to add a Table-View M2O Relation When only having a t2_view.c2 source column, we need to add a Table-View M2O Relationship
t1.c1----t2.c2 t1.c1----------t2.c2 t1.c1----t2.c2 t1.c1----------t2.c2
-> \________ -> \________
\ \
t2_view.c2 t2_view.c1 t2_view.c2 t2_view.c1
When having t1_view.c1 and a t2_view.c2 source columns, we need to add a View-View M2O Relation in addition to the prior When having t1_view.c1 and a t2_view.c2 source columns, we need to add a View-View M2O Relationship in addition to the prior
t1.c1----t2.c2 t1.c1----------t2.c2 t1.c1----t2.c2 t1.c1----------t2.c2
-> \________/ -> \________/
/ \ / \
t1_view.c1 t2_view.c2 t1_view.c1-------t2_view.c1 t1_view.c1 t2_view.c2 t1_view.c1-------t2_view.c1
The logic for composite pks is similar just need to make sure all the Relation columns have source columns. The logic for composite pks is similar just need to make sure all the Relationship columns have source columns.
-} -}
addViewM2ORels :: [SourceColumn] -> [Relation] -> [Relation] addViewM2ORels :: [SourceColumn] -> [Relationship] -> [Relationship]
addViewM2ORels allSrcCols = concatMap (\rel@Relation{..} -> rel : addViewM2ORels allSrcCols = concatMap (\rel@Relationship{..} -> rel :
let let
srcColsGroupedByView :: [Column] -> [[SourceColumn]] srcColsGroupedByView :: [Column] -> [[SourceColumn]]
srcColsGroupedByView relCols = L.groupBy (\(_, viewCol1) (_, viewCol2) -> colTable viewCol1 == colTable viewCol2) $ srcColsGroupedByView relCols = L.groupBy (\(_, viewCol1) (_, viewCol2) -> colTable viewCol1 == colTable viewCol2) $
@@ -397,26 +397,26 @@ addViewM2ORels allSrcCols = concatMap (\rel@Relation{..} -> rel :
getView :: [SourceColumn] -> Table getView :: [SourceColumn] -> Table
getView = colTable . snd . unsafeHead getView = colTable . snd . unsafeHead
srcCols `allSrcColsOf` cols = S.fromList (fst <$> srcCols) == S.fromList cols srcCols `allSrcColsOf` cols = S.fromList (fst <$> srcCols) == S.fromList cols
-- Relation is dependent on the order of relColumns and relFColumns to get the join conditions right in the generated query. -- Relationship is dependent on the order of relColumns and relFColumns to get the join conditions right in the generated query.
-- So we need to change the order of the SourceColumns to match the relColumns -- So we need to change the order of the SourceColumns to match the relColumns
-- TODO: This could be avoided if the Relation type is improved with a structure that maintains the association of relColumns and relFColumns -- TODO: This could be avoided if the Relationship type is improved with a structure that maintains the association of relColumns and relFColumns
srcCols `sortAccordingTo` cols = sortOn (\(k, _) -> L.lookup k $ zip cols [0::Int ..]) srcCols srcCols `sortAccordingTo` cols = sortOn (\(k, _) -> L.lookup k $ zip cols [0::Int ..]) srcCols
viewTableM2O = viewTableM2O =
[ Relation [ Relationship
(getView srcCols) (snd <$> srcCols `sortAccordingTo` relColumns) (getView srcCols) (snd <$> srcCols `sortAccordingTo` relColumns)
relForeignTable relForeignColumns relCardinality relForeignTable relForeignColumns relCardinality
| srcCols <- relSrcCols, srcCols `allSrcColsOf` relColumns ] | srcCols <- relSrcCols, srcCols `allSrcColsOf` relColumns ]
tableViewM2O = tableViewM2O =
[ Relation [ Relationship
relTable relColumns relTable relColumns
(getView fSrcCols) (snd <$> fSrcCols `sortAccordingTo` relForeignColumns) (getView fSrcCols) (snd <$> fSrcCols `sortAccordingTo` relForeignColumns)
relCardinality relCardinality
| fSrcCols <- relFSrcCols, fSrcCols `allSrcColsOf` relForeignColumns ] | fSrcCols <- relFSrcCols, fSrcCols `allSrcColsOf` relForeignColumns ]
viewViewM2O = viewViewM2O =
[ Relation [ Relationship
(getView srcCols) (snd <$> srcCols `sortAccordingTo` relColumns) (getView srcCols) (snd <$> srcCols `sortAccordingTo` relColumns)
(getView fSrcCols) (snd <$> fSrcCols `sortAccordingTo` relForeignColumns) (getView fSrcCols) (snd <$> fSrcCols `sortAccordingTo` relForeignColumns)
relCardinality relCardinality
@@ -425,14 +425,14 @@ addViewM2ORels allSrcCols = concatMap (\rel@Relation{..} -> rel :
in viewTableM2O ++ tableViewM2O ++ viewViewM2O) in viewTableM2O ++ tableViewM2O ++ viewViewM2O)
addO2MRels :: [Relation] -> [Relation] addO2MRels :: [Relationship] -> [Relationship]
addO2MRels rels = rels ++ [ Relation ft fc t c (O2M cons) addO2MRels rels = rels ++ [ Relationship ft fc t c (O2M cons)
| Relation t c ft fc (M2O cons) <- rels ] | Relationship t c ft fc (M2O cons) <- rels ]
addM2MRels :: [Relation] -> [Relation] addM2MRels :: [Relationship] -> [Relationship]
addM2MRels rels = rels ++ [ Relation t c ft fc (M2M $ Junction jt1 cons1 jc1 cons2 jc2) addM2MRels rels = rels ++ [ Relationship t c ft fc (M2M $ Junction jt1 cons1 jc1 cons2 jc2)
| Relation jt1 jc1 t c (M2O cons1) <- rels | Relationship jt1 jc1 t c (M2O cons1) <- rels
, Relation jt2 jc2 ft fc (M2O cons2) <- rels , Relationship jt2 jc2 ft fc (M2O cons2) <- rels
, jt1 == jt2 , jt1 == jt2
, cons1 /= cons2] , cons1 /= cons2]
@@ -602,7 +602,7 @@ columnFromRow tabs (s, t, n, desc, nul, typ, l, d, e) = buildColumn <$> table
parseEnum :: Maybe Text -> [Text] parseEnum :: Maybe Text -> [Text]
parseEnum = maybe [] (split (==',')) parseEnum = maybe [] (split (==','))
allM2ORels :: [Table] -> [Column] -> Bool -> H.Statement () [Relation] allM2ORels :: [Table] -> [Column] -> Bool -> H.Statement () [Relationship]
allM2ORels tabs cols = allM2ORels tabs cols =
H.Statement sql HE.noParams (decodeRels tabs cols) H.Statement sql HE.noParams (decodeRels tabs cols)
where where
@@ -629,9 +629,9 @@ allM2ORels tabs cols =
WHERE confrelid != 0 WHERE confrelid != 0
ORDER BY (conrelid, column_info.nums) |] ORDER BY (conrelid, column_info.nums) |]
relFromRow :: [Table] -> [Column] -> (Text, Text, Text, [Text], Text, Text, [Text]) -> Maybe Relation relFromRow :: [Table] -> [Column] -> (Text, Text, Text, [Text], Text, Text, [Text]) -> Maybe Relationship
relFromRow allTabs allCols (rs, rt, cn, rcs, frs, frt, frcs) = relFromRow allTabs allCols (rs, rt, cn, rcs, frs, frt, frcs) =
Relation <$> table <*> cols <*> tableF <*> colsF <*> pure (M2O cn) Relationship <$> table <*> cols <*> tableF <*> colsF <*> pure (M2O cn)
where where
findTable s t = find (\tbl -> tableSchema tbl == s && tableName tbl == t) allTabs findTable s t = find (\tbl -> tableSchema tbl == s && tableName tbl == t) allTabs
findCol s t c = find (\col -> tableSchema (colTable col) == s && tableName (colTable col) == t && colName col == c) allCols findCol s t c = find (\col -> tableSchema (colTable col) == s && tableName (colTable col) == t && colName col == c) allCols
@@ -1,11 +1,11 @@
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric #-}
module PostgREST.DbStructure.Relation module PostgREST.DbStructure.Relationship
( Cardinality(..) ( Cardinality(..)
, ForeignKey(..) , ForeignKey(..)
, PrimaryKey(..) , PrimaryKey(..)
, Relation(..) , Relationship(..)
, Junction(..) , Junction(..)
, isSelfReference , isSelfReference
) where ) where
@@ -18,13 +18,13 @@ import PostgREST.DbStructure.Table (Column (..), ForeignKey (..),
import Protolude import Protolude
-- | "Relation"ship between two tables. -- | Relationship between two tables.
-- --
-- The order of the relColumns and relFColumns should be maintained to get the -- The order of the relColumns and relForeignColumns should be maintained to get the
-- join conditions right. -- join conditions right.
-- --
-- TODO merge relColumns and relFColumns to a tuple or Data.Bimap -- TODO merge relColumns and relForeignColumns to a tuple or Data.Bimap
data Relation = Relation data Relationship = Relationship
{ relTable :: Table { relTable :: Table
, relColumns :: [Column] , relColumns :: [Column]
, relForeignTable :: Table , relForeignTable :: Table
@@ -54,7 +54,7 @@ data Junction = Junction
} }
deriving (Eq, Generic, JSON.ToJSON) deriving (Eq, Generic, JSON.ToJSON)
isSelfReference :: Relation -> Bool isSelfReference :: Relationship -> Bool
isSelfReference r = relTable r == relForeignTable r isSelfReference r = relTable r == relForeignTable r
data PrimaryKey = PrimaryKey data PrimaryKey = PrimaryKey
+6 -9
View File
@@ -29,8 +29,9 @@ import Network.HTTP.Types.Header (Header)
import PostgREST.ContentType (ContentType (..)) import PostgREST.ContentType (ContentType (..))
import qualified PostgREST.ContentType as ContentType import qualified PostgREST.ContentType as ContentType
import PostgREST.DbStructure.Relation (Cardinality (..), import PostgREST.DbStructure.Relationship (Cardinality (..),
Junction (..), Relation (..)) Junction (..),
Relationship (..))
import PostgREST.DbStructure.Table (Column (..), Table (..)) import PostgREST.DbStructure.Table (Column (..), Table (..))
import Protolude hiding (toS) import Protolude hiding (toS)
@@ -55,10 +56,9 @@ data ApiRequestError
| InvalidBody ByteString | InvalidBody ByteString
| ParseRequestError Text Text | ParseRequestError Text Text
| NoRelBetween Text Text | NoRelBetween Text Text
| AmbiguousRelBetween Text Text [Relation] | AmbiguousRelBetween Text Text [Relationship]
| InvalidFilters | InvalidFilters
| UnacceptableSchema [Text] | UnacceptableSchema [Text]
| UnknownRelation -- Unreachable?
| UnsupportedVerb -- Unreachable? | UnsupportedVerb -- Unreachable?
instance PgrstError ApiRequestError where instance PgrstError ApiRequestError where
@@ -66,7 +66,6 @@ instance PgrstError ApiRequestError where
status InvalidFilters = HT.status405 status InvalidFilters = HT.status405
status (InvalidBody _) = HT.status400 status (InvalidBody _) = HT.status400
status UnsupportedVerb = HT.status405 status UnsupportedVerb = HT.status405
status UnknownRelation = HT.status404
status ActionInappropriate = HT.status405 status ActionInappropriate = HT.status405
status (ParseRequestError _ _) = HT.status400 status (ParseRequestError _ _) = HT.status400
status (NoRelBetween _ _) = HT.status400 status (NoRelBetween _ _) = HT.status400
@@ -84,8 +83,6 @@ instance JSON.ToJSON ApiRequestError where
"message" .= (toS errorMessage :: Text)] "message" .= (toS errorMessage :: Text)]
toJSON InvalidRange = JSON.object [ toJSON InvalidRange = JSON.object [
"message" .= ("HTTP Range error" :: Text)] "message" .= ("HTTP Range error" :: Text)]
toJSON UnknownRelation = JSON.object [
"message" .= ("Unknown relation" :: Text)]
toJSON (NoRelBetween parent child) = JSON.object [ toJSON (NoRelBetween parent child) = JSON.object [
"message" .= ("Could not find foreign keys between these entities. No relationship found between " <> parent <> " and " <> child :: Text)] "message" .= ("Could not find foreign keys between these entities. No relationship found between " <> parent <> " and " <> child :: Text)]
toJSON (AmbiguousRelBetween parent child rels) = JSON.object [ toJSON (AmbiguousRelBetween parent child rels) = JSON.object [
@@ -99,8 +96,8 @@ instance JSON.ToJSON ApiRequestError where
toJSON (UnacceptableSchema schemas) = JSON.object [ toJSON (UnacceptableSchema schemas) = JSON.object [
"message" .= ("The schema must be one of the following: " <> T.intercalate ", " schemas)] "message" .= ("The schema must be one of the following: " <> T.intercalate ", " schemas)]
compressedRel :: Relation -> JSON.Value compressedRel :: Relationship -> JSON.Value
compressedRel Relation{..} = compressedRel Relationship{..} =
let let
fmtTbl Table{..} = tableSchema <> "." <> tableName fmtTbl Table{..} = tableSchema <> "." <> tableName
fmtEls els = "[" <> T.intercalate ", " els <> "]" fmtEls els = "[" <> T.intercalate ", " els <> "]"
+5 -4
View File
@@ -23,12 +23,13 @@ import Data.Swagger
import PostgREST.Config (AppConfig (..), Proxy (..), import PostgREST.Config (AppConfig (..), Proxy (..),
isMalformedProxyUri, toURI) isMalformedProxyUri, toURI)
import PostgREST.DbStructure (DbStructure (..), tableCols, import PostgREST.DbStructure (DbStructure (..),
tablePKCols) tableCols, tablePKCols)
import PostgREST.DbStructure.Proc (PgArg (..), import PostgREST.DbStructure.Proc (PgArg (..),
ProcDescription (..)) ProcDescription (..))
import PostgREST.DbStructure.Relation (PrimaryKey (..)) import PostgREST.DbStructure.Relationship (PrimaryKey (..))
import PostgREST.DbStructure.Table (Column (..), ForeignKey (..), import PostgREST.DbStructure.Table (Column (..),
ForeignKey (..),
Table (..)) Table (..))
import PostgREST.Version (docsVersion, prettyVersion) import PostgREST.Version (docsVersion, prettyVersion)
+3 -3
View File
@@ -24,8 +24,8 @@ import Data.Tree (Tree (..))
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (FieldName,
QualifiedIdentifier (..)) QualifiedIdentifier (..))
import PostgREST.DbStructure.Proc (PgArg (..)) import PostgREST.DbStructure.Proc (PgArg (..))
import PostgREST.DbStructure.Relation (Cardinality (..), import PostgREST.DbStructure.Relationship (Cardinality (..),
Relation (..)) Relationship (..))
import PostgREST.DbStructure.Table (Table (..)) import PostgREST.DbStructure.Table (Table (..))
import PostgREST.Request.ApiRequest (PayloadJSON (..)) import PostgREST.Request.ApiRequest (PayloadJSON (..))
import PostgREST.Request.Preferences (PreferParameters (..), import PostgREST.Request.Preferences (PreferParameters (..),
@@ -53,7 +53,7 @@ readRequestToQuery (Node (Select colSelects mainQi tblAlias implJoins logicFores
(joins, selects) = foldr getJoinsSelects ([],[]) forest (joins, selects) = foldr getJoinsSelects ([],[]) forest
getJoinsSelects :: ReadRequest -> ([H.Snippet], [H.Snippet]) -> ([H.Snippet], [H.Snippet]) getJoinsSelects :: ReadRequest -> ([H.Snippet], [H.Snippet]) -> ([H.Snippet], [H.Snippet])
getJoinsSelects rr@(Node (_, (name, Just Relation{relCardinality=card,relTable=Table{tableName=table}}, alias, _, _)) _) (j,s) = getJoinsSelects rr@(Node (_, (name, Just Relationship{relCardinality=card,relTable=Table{tableName=table}}, alias, _, _)) _) (j,s) =
let subquery = readRequestToQuery rr in let subquery = readRequestToQuery rr in
case card of case card of
M2O _ -> M2O _ ->
+17 -17
View File
@@ -33,9 +33,9 @@ import Data.Tree (Tree (..))
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (FieldName,
QualifiedIdentifier (..), QualifiedIdentifier (..),
Schema, TableName) Schema, TableName)
import PostgREST.DbStructure.Relation (Cardinality (..), import PostgREST.DbStructure.Relationship (Cardinality (..),
Junction (..), Junction (..),
Relation (..)) Relationship (..))
import PostgREST.DbStructure.Table (Column (..), Table (..), import PostgREST.DbStructure.Table (Column (..), Table (..),
tableQi) tableQi)
import PostgREST.Error (ApiRequestError (..), import PostgREST.Error (ApiRequestError (..),
@@ -51,14 +51,14 @@ import PostgREST.Request.Parsers
import PostgREST.Request.Preferences import PostgREST.Request.Preferences
import PostgREST.Request.Types import PostgREST.Request.Types
import qualified PostgREST.DbStructure.Relation as Relation import qualified PostgREST.DbStructure.Relationship as Relationship
import Protolude hiding (from) import Protolude hiding (from)
-- | Builds the ReadRequest tree on a number of stages. -- | Builds the ReadRequest tree on a number of stages.
-- | Adds filters, order, limits on its respective nodes. -- | Adds filters, order, limits on its respective nodes.
-- | Adds joins conditions obtained from resource embedding. -- | Adds joins conditions obtained from resource embedding.
readRequest :: Schema -> TableName -> Maybe Integer -> [Relation] -> ApiRequest -> Either Error ReadRequest readRequest :: Schema -> TableName -> Maybe Integer -> [Relationship] -> ApiRequest -> Either Error ReadRequest
readRequest schema rootTableName maxRows allRels apiRequest = readRequest schema rootTableName maxRows allRels apiRequest =
mapLeft ApiRequestError $ mapLeft ApiRequestError $
treeRestrictRange maxRows =<< treeRestrictRange maxRows =<<
@@ -73,17 +73,17 @@ readRequest schema rootTableName maxRows allRels apiRequest =
-- This is done because of the shape of the final SQL Query. The mutation cases -- This is done because of the shape of the final SQL Query. The mutation cases
-- are wrapped in a WITH {sourceCTEName}(see Statements.hs). So we need a FROM -- are wrapped in a WITH {sourceCTEName}(see Statements.hs). So we need a FROM
-- {sourceCTEName} instead of FROM {tableName}. -- {sourceCTEName} instead of FROM {tableName}.
rootWithRels :: Schema -> TableName -> [Relation] -> Action -> (QualifiedIdentifier, [Relation]) rootWithRels :: Schema -> TableName -> [Relationship] -> Action -> (QualifiedIdentifier, [Relationship])
rootWithRels schema rootTableName allRels action = case action of rootWithRels schema rootTableName allRels action = case action of
ActionRead _ -> (QualifiedIdentifier schema rootTableName, allRels) -- normal read case ActionRead _ -> (QualifiedIdentifier schema rootTableName, allRels) -- normal read case
_ -> (QualifiedIdentifier mempty _sourceCTEName, mapMaybe toSourceRel allRels ++ allRels) -- mutation cases and calling proc _ -> (QualifiedIdentifier mempty _sourceCTEName, mapMaybe toSourceRel allRels ++ allRels) -- mutation cases and calling proc
where where
_sourceCTEName = decodeUtf8 sourceCTEName _sourceCTEName = decodeUtf8 sourceCTEName
-- To enable embedding in the sourceCTEName cases we need to replace the -- To enable embedding in the sourceCTEName cases we need to replace the
-- foreign key tableName in the Relation with {sourceCTEName}. This way -- foreign key tableName in the Relationship with {sourceCTEName}. This way
-- findRel can find relationships with sourceCTEName. -- findRel can find relationships with sourceCTEName.
toSourceRel :: Relation -> Maybe Relation toSourceRel :: Relationship -> Maybe Relationship
toSourceRel r@Relation{relTable=t} toSourceRel r@Relationship{relTable=t}
| rootTableName == tableName t = Just $ r {relTable=t {tableName=_sourceCTEName}} | rootTableName == tableName t = Just $ r {relTable=t {tableName=_sourceCTEName}}
| otherwise = Nothing | otherwise = Nothing
@@ -117,12 +117,12 @@ treeRestrictRange maxRows request = pure $ nodeRestrictRange maxRows <$> request
nodeRestrictRange :: Maybe Integer -> ReadNode -> ReadNode nodeRestrictRange :: Maybe Integer -> ReadNode -> ReadNode
nodeRestrictRange m (q@Select {range_=r}, i) = (q{range_=restrictRange m r }, i) nodeRestrictRange m (q@Select {range_=r}, i) = (q{range_=restrictRange m r }, i)
augmentRequestWithJoin :: Schema -> [Relation] -> ReadRequest -> Either ApiRequestError ReadRequest augmentRequestWithJoin :: Schema -> [Relationship] -> ReadRequest -> Either ApiRequestError ReadRequest
augmentRequestWithJoin schema allRels request = augmentRequestWithJoin schema allRels request =
addRels schema allRels Nothing request addRels schema allRels Nothing request
>>= addJoinConditions Nothing >>= addJoinConditions Nothing
addRels :: Schema -> [Relation] -> Maybe ReadRequest -> ReadRequest -> Either ApiRequestError ReadRequest addRels :: Schema -> [Relationship] -> Maybe ReadRequest -> ReadRequest -> Either ApiRequestError ReadRequest
addRels schema allRels parentNode (Node (query@Select{from=tbl}, (nodeName, _, alias, hint, depth)) forest) = addRels schema allRels parentNode (Node (query@Select{from=tbl}, (nodeName, _, alias, hint, depth)) forest) =
case parentNode of case parentNode of
Just (Node (Select{from=parentNodeQi}, _) _) -> Just (Node (Select{from=parentNodeQi}, _) _) ->
@@ -148,7 +148,7 @@ addRels schema allRels parentNode (Node (query@Select{from=tbl}, (nodeName, _, a
-- target = table / view / constraint / column-from-origin -- target = table / view / constraint / column-from-origin
-- hint = table / view / constraint / column-from-origin / column-from-target -- hint = table / view / constraint / column-from-origin / column-from-target
-- (hint can take table / view values to aid in finding the junction in an m2m relationship) -- (hint can take table / view values to aid in finding the junction in an m2m relationship)
findRel :: Schema -> [Relation] -> NodeName -> NodeName -> Maybe EmbedHint -> Either ApiRequestError Relation findRel :: Schema -> [Relationship] -> NodeName -> NodeName -> Maybe EmbedHint -> Either ApiRequestError Relationship
findRel schema allRels origin target hint = findRel schema allRels origin target hint =
case rel of case rel of
[] -> Left $ NoRelBetween origin target [] -> Left $ NoRelBetween origin target
@@ -173,7 +173,7 @@ findRel schema allRels origin target hint =
M2M Junction{junTable} -> hint_ == Just (tableName junTable) M2M Junction{junTable} -> hint_ == Just (tableName junTable)
_ -> False _ -> False
rel = filter ( rel = filter (
\Relation{..} -> \Relationship{..} ->
-- Both relationship ends need to be on the exposed schema -- Both relationship ends need to be on the exposed schema
schema == tableSchema relTable && schema == tableSchema relForeignTable && schema == tableSchema relTable && schema == tableSchema relForeignTable &&
( (
@@ -210,13 +210,13 @@ findRel schema allRels origin target hint =
addJoinConditions :: Maybe Alias -> ReadRequest -> Either ApiRequestError ReadRequest addJoinConditions :: Maybe Alias -> ReadRequest -> Either ApiRequestError ReadRequest
addJoinConditions previousAlias (Node node@(query@Select{from=tbl}, nodeProps@(_, rel, _, _, depth)) forest) = addJoinConditions previousAlias (Node node@(query@Select{from=tbl}, nodeProps@(_, rel, _, _, depth)) forest) =
case rel of case rel of
Just r@Relation{relCardinality=M2M Junction{junTable}} -> Just r@Relationship{relCardinality=M2M Junction{junTable}} ->
let rq = augmentQuery r in let rq = augmentQuery r in
Node (rq{implicitJoins=tableQi junTable:implicitJoins rq}, nodeProps) <$> updatedForest Node (rq{implicitJoins=tableQi junTable:implicitJoins rq}, nodeProps) <$> updatedForest
Just r -> Node (augmentQuery r, nodeProps) <$> updatedForest Just r -> Node (augmentQuery r, nodeProps) <$> updatedForest
Nothing -> Node node <$> updatedForest Nothing -> Node node <$> updatedForest
where where
newAlias = case Relation.isSelfReference <$> rel of newAlias = case Relationship.isSelfReference <$> rel of
Just True Just True
| depth /= 0 -> Just (qiName tbl <> "_" <> show depth) -- root node doesn't get aliased | depth /= 0 -> Just (qiName tbl <> "_" <> show depth) -- root node doesn't get aliased
| otherwise -> Nothing | otherwise -> Nothing
@@ -229,8 +229,8 @@ addJoinConditions previousAlias (Node node@(query@Select{from=tbl}, nodeProps@(_
updatedForest = addJoinConditions newAlias `traverse` forest updatedForest = addJoinConditions newAlias `traverse` forest
-- previousAlias and newAlias are used in the case of self joins -- previousAlias and newAlias are used in the case of self joins
getJoinConditions :: Maybe Alias -> Maybe Alias -> Relation -> [JoinCondition] getJoinConditions :: Maybe Alias -> Maybe Alias -> Relationship -> [JoinCondition]
getJoinConditions previousAlias newAlias (Relation Table{tableSchema=tSchema, tableName=tN} cols Table{tableName=ftN} fCols card) = getJoinConditions previousAlias newAlias (Relationship Table{tableSchema=tSchema, tableName=tN} cols Table{tableName=ftN} fCols card) =
case card of case card of
M2M (Junction Table{tableName=jtn} _ jc1 _ jc2) -> M2M (Junction Table{tableName=jtn} _ jc1 _ jc2) ->
zipWith (toJoinCondition tN jtn) cols jc1 ++ zipWith (toJoinCondition ftN jtn) fCols jc2 zipWith (toJoinCondition tN jtn) cols jc1 ++ zipWith (toJoinCondition ftN jtn) fCols jc2
@@ -359,7 +359,7 @@ returningCols rr@(Node _ forest) pkCols
-- projects. So this adds the foreign key columns to ensure the embedding -- projects. So this adds the foreign key columns to ensure the embedding
-- succeeds, result would be `RETURNING name, client_id`. -- succeeds, result would be `RETURNING name, client_id`.
fkCols = concat $ mapMaybe (\case fkCols = concat $ mapMaybe (\case
Node (_, (_, Just Relation{relColumns=cols}, _, _, _)) _ -> Just cols Node (_, (_, Just Relationship{relColumns=cols}, _, _, _)) _ -> Just cols
_ -> Nothing _ -> Nothing
) forest ) forest
-- However if the "client_id" is present, e.g. mutateRequest to -- However if the "client_id" is present, e.g. mutateRequest to
+3 -6
View File
@@ -38,7 +38,7 @@ import qualified GHC.Show (show)
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (FieldName,
QualifiedIdentifier) QualifiedIdentifier)
import PostgREST.DbStructure.Relation (Relation) import PostgREST.DbStructure.Relationship (Relationship)
import PostgREST.RangeQuery (NonnegRange) import PostgREST.RangeQuery (NonnegRange)
import PostgREST.Request.Preferences (PreferResolution) import PostgREST.Request.Preferences (PreferResolution)
@@ -49,7 +49,7 @@ type ReadRequest = Tree ReadNode
type MutateRequest = MutateQuery type MutateRequest = MutateQuery
type ReadNode = type ReadNode =
(ReadQuery, (NodeName, Maybe Relation, Maybe Alias, Maybe EmbedHint, Depth)) (ReadQuery, (NodeName, Maybe Relationship, Maybe Alias, Maybe EmbedHint, Depth))
type NodeName = Text type NodeName = Text
type Depth = Integer type Depth = Integer
@@ -121,10 +121,7 @@ data MutateQuery
, returning :: [FieldName] , returning :: [FieldName]
} }
-- | This type will hold information about which particular 'Relation' between -- | The select value in `/tbl?select=alias:field::cast`
-- two tables to choose when there are multiple ones.
-- Specifically, it will contain the name of the foreign key or the join table
-- in many to many relations.
type SelectItem = (Field, Maybe Cast, Maybe Alias, Maybe EmbedHint) type SelectItem = (Field, Maybe Cast, Maybe Alias, Maybe EmbedHint)
type Field = (FieldName, JsonPath) type Field = (FieldName, JsonPath)