refactor: Relation to Relationship
Also remove the unused UnknownRelationship error.
This commit is contained in:
committed by
Steve Chavez
parent
7ca0d46936
commit
21d280497b
+1
-1
@@ -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
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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 <> "]"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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 _ ->
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user