Add constraint_name key to *_suggest_relationships Metadata API and increase sensitivity of tables argument

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7625
GitOrigin-RevId: d764f1664b63abbe4a4ff166e0bc7224bcb0dc57
This commit is contained in:
Lyndon Maydwell 2023-01-24 20:26:08 +10:00 committed by hasura-bot
parent d15af96f10
commit 615fd64c04
2 changed files with 84 additions and 35 deletions

View File

@ -11,7 +11,7 @@ import Harness.Quoter.Yaml (yaml)
import Harness.Test.Fixture qualified as Fixture import Harness.Test.Fixture qualified as Fixture
import Harness.Test.Schema qualified as Schema import Harness.Test.Schema qualified as Schema
import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment, getBackendTypeConfig) import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment, getBackendTypeConfig)
import Harness.Yaml (mapObject, shouldReturnYaml, shouldReturnYamlF, sortArray) import Harness.Yaml (mapObject, shouldReturnYamlF, sortArray)
import Hasura.Prelude import Hasura.Prelude
import Test.Hspec (SpecWith, describe, it) import Test.Hspec (SpecWith, describe, it)
@ -132,7 +132,8 @@ tests opts = do
sourceName = Fixture.backendSourceName backendTypeMetadata sourceName = Fixture.backendSourceName backendTypeMetadata
schemaName = Schema.unSchemaName $ Schema.getSchemaName testEnv schemaName = Schema.unSchemaName $ Schema.getSchemaName testEnv
shouldReturnYaml shouldReturnYamlF
(pure . mapObject sortArray)
opts opts
( GraphqlEngine.postMetadataWithStatus ( GraphqlEngine.postMetadataWithStatus
200 200
@ -153,7 +154,8 @@ tests opts = do
relationships: relationships:
- from: - from:
columns: columns:
- publication_id - author_id
constraint_name: article_author_id_fkey
table: table:
name: article name: article
schema: hasura schema: hasura
@ -161,9 +163,23 @@ tests opts = do
columns: columns:
- id - id
table: table:
name: publication name: author
schema: hasura schema: hasura
type: object type: object
- from:
columns:
- id
table:
name: author
schema: hasura
to:
columns:
- author_id
constraint_name: article_author_id_fkey
table:
name: article
schema: hasura
type: array
- from: - from:
columns: columns:
- id - id
@ -173,17 +189,33 @@ tests opts = do
to: to:
columns: columns:
- publication_id - publication_id
constraint_name: article_publication_id_fkey
table: table:
name: article name: article
schema: hasura schema: hasura
type: object type: object
- from:
columns:
- publication_id
constraint_name: article_publication_id_fkey
table:
name: article
schema: hasura
to:
columns:
- id
table:
name: publication
schema: hasura
type: object
|] |]
it "Omits tracked relationships if that is requested" $ \testEnv -> do it "Omits tracked relationships if that is requested" $ \testEnv -> do
let backendTypeMetadata = Maybe.fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnv let backendTypeMetadata = Maybe.fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnv
sourceName = Fixture.backendSourceName backendTypeMetadata sourceName = Fixture.backendSourceName backendTypeMetadata
shouldReturnYaml shouldReturnYamlF
(pure . mapObject sortArray)
opts opts
( GraphqlEngine.postMetadataWithStatus ( GraphqlEngine.postMetadataWithStatus
200 200
@ -201,6 +233,7 @@ tests opts = do
- from: - from:
columns: columns:
- author_id - author_id
constraint_name: article_author_id_fkey
table: table:
name: article name: article
schema: hasura schema: hasura
@ -220,6 +253,7 @@ tests opts = do
to: to:
columns: columns:
- author_id - author_id
constraint_name: article_author_id_fkey
table: table:
name: article name: article
schema: hasura schema: hasura
@ -230,7 +264,8 @@ tests opts = do
let backendTypeMetadata = Maybe.fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnv let backendTypeMetadata = Maybe.fromMaybe (error "Unknown backend") $ getBackendTypeConfig testEnv
sourceName = Fixture.backendSourceName backendTypeMetadata sourceName = Fixture.backendSourceName backendTypeMetadata
shouldReturnYaml shouldReturnYamlF
(pure . mapObject sortArray)
opts opts
( GraphqlEngine.postMetadataWithStatus ( GraphqlEngine.postMetadataWithStatus
200 200
@ -267,6 +302,7 @@ tests opts = do
- from: - from:
columns: columns:
- author_id - author_id
constraint_name: article_author_id_fkey
table: table:
name: article name: article
schema: hasura schema: hasura
@ -286,6 +322,7 @@ tests opts = do
to: to:
columns: columns:
- author_id - author_id
constraint_name: article_author_id_fkey
table: table:
name: article name: article
schema: hasura schema: hasura
@ -299,6 +336,7 @@ tests opts = do
to: to:
columns: columns:
- publication_id - publication_id
constraint_name: article_publication_id_fkey
table: table:
name: article name: article
schema: hasura schema: hasura
@ -306,6 +344,7 @@ tests opts = do
- from: - from:
columns: columns:
- publication_id - publication_id
constraint_name: article_publication_id_fkey
table: table:
name: article name: article
schema: hasura schema: hasura

View File

@ -24,7 +24,7 @@ where
import Autodocodec import Autodocodec
import Autodocodec.OpenAPI () import Autodocodec.OpenAPI ()
import Control.Lens (preview) import Control.Lens (preview)
import Data.Aeson (FromJSON (), ToJSON ()) import Data.Aeson qualified as Aeson
import Data.HashMap.Strict qualified as Map import Data.HashMap.Strict qualified as Map
import Data.HashMap.Strict.NonEmpty qualified as MapNE import Data.HashMap.Strict.NonEmpty qualified as MapNE
import Data.HashSet qualified as H import Data.HashSet qualified as H
@ -38,7 +38,7 @@ import Hasura.RQL.Types.Metadata.Backend
import Hasura.RQL.Types.Relationships.Local (RelInfo (riMapping, riRTable)) import Hasura.RQL.Types.Relationships.Local (RelInfo (riMapping, riRTable))
import Hasura.RQL.Types.SchemaCache import Hasura.RQL.Types.SchemaCache
import Hasura.RQL.Types.SchemaCache.Build import Hasura.RQL.Types.SchemaCache.Build
import Hasura.RQL.Types.Table (ForeignKey, UniqueConstraint, _fkColumnMapping, _fkForeignTable, _ucColumns) import Hasura.RQL.Types.Table (ForeignKey, UniqueConstraint, _cName, _fkColumnMapping, _fkConstraint, _fkForeignTable, _ucColumns)
-- | Datatype used by Metadata API to represent Request for Suggested Relationships -- | Datatype used by Metadata API to represent Request for Suggested Relationships
data SuggestRels b = SuggestRels data SuggestRels b = SuggestRels
@ -47,7 +47,7 @@ data SuggestRels b = SuggestRels
_srsOmitTracked :: Bool _srsOmitTracked :: Bool
} }
deriving (Generic) deriving (Generic)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec (SuggestRels b) deriving (Aeson.FromJSON, Aeson.ToJSON, ToSchema) via Autodocodec (SuggestRels b)
instance Backend b => HasCodec (SuggestRels b) where instance Backend b => HasCodec (SuggestRels b) where
codec = codec =
@ -67,7 +67,7 @@ newtype SuggestedRelationships b = Relationships
{ sRelationships :: [Relationship b] { sRelationships :: [Relationship b]
} }
deriving (Generic) deriving (Generic)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec (SuggestedRelationships b) deriving (Aeson.FromJSON, Aeson.ToJSON, ToSchema) via Autodocodec (SuggestedRelationships b)
instance Backend b => HasCodec (SuggestedRelationships b) where instance Backend b => HasCodec (SuggestedRelationships b) where
codec = codec =
@ -84,7 +84,7 @@ data Relationship b = Relationship
rTo :: Mapping b rTo :: Mapping b
} }
deriving (Generic) deriving (Generic)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec (Relationship b) deriving (Aeson.FromJSON, Aeson.ToJSON, ToSchema) via Autodocodec (Relationship b)
instance Backend b => HasCodec (Relationship b) where instance Backend b => HasCodec (Relationship b) where
codec = codec =
@ -101,10 +101,11 @@ instance Backend b => HasCodec (Relationship b) where
data Mapping b = Mapping data Mapping b = Mapping
{ mTable :: TableName b, { mTable :: TableName b,
mColumns :: [Column b] mColumns :: [Column b],
mConstraintName :: Maybe Aeson.Value
} }
deriving (Generic) deriving (Generic)
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec (Mapping b) deriving (Aeson.FromJSON, Aeson.ToJSON, ToSchema) via Autodocodec (Mapping b)
instance Backend b => HasCodec (Mapping b) where instance Backend b => HasCodec (Mapping b) where
codec = codec =
@ -115,6 +116,8 @@ instance Backend b => HasCodec (Mapping b) where
.= mTable .= mTable
<*> requiredField' "columns" <*> requiredField' "columns"
.= mColumns .= mColumns
<*> optionalFieldOrNull' "constraint_name"
.= mConstraintName
) )
-- | Most of the heavy lifting for this module occurs in this function. -- | Most of the heavy lifting for this module occurs in this function.
@ -130,22 +133,25 @@ suggestRelsFK ::
TableName b -> TableName b ->
HashSet (UniqueConstraint b) -> HashSet (UniqueConstraint b) ->
H.HashSet (TableName b, HashMap (Column b) (Column b)) -> H.HashSet (TableName b, HashMap (Column b) (Column b)) ->
(TableName b -> Bool) ->
ForeignKey b -> ForeignKey b ->
[Relationship b] [Relationship b]
suggestRelsFK omitTracked tables name uniqueConstraints tracked foreignKey = suggestRelsFK omitTracked tables name uniqueConstraints tracked predicate foreignKey =
case (omitTracked, H.member (relatedTable, columnRelationships) tracked, Map.lookup relatedTable tables) of case (omitTracked, H.member (relatedTable, columnRelationships) tracked, Map.lookup relatedTable tables) of
(True, True, _) -> [] (True, True, _) -> []
(_, _, Nothing) -> [] (_, _, Nothing) -> []
(_, _, Just _) -> (_, _, Just _)
| not (predicate name || predicate relatedTable) -> []
| otherwise ->
[ Relationship [ Relationship
{ rType = ObjRel, { rType = ObjRel,
rFrom = Mapping {mTable = name, mColumns = localColumns}, rFrom = Mapping {mTable = name, mColumns = localColumns, mConstraintName = Just constraintName},
rTo = Mapping {mTable = relatedTable, mColumns = relatedColumns} rTo = Mapping {mTable = relatedTable, mColumns = relatedColumns, mConstraintName = Nothing}
}, },
Relationship Relationship
{ rType = if H.fromList localColumns `H.member` uniqueConstraintColumns then ObjRel else ArrRel, { rType = if H.fromList localColumns `H.member` uniqueConstraintColumns then ObjRel else ArrRel,
rTo = Mapping {mTable = name, mColumns = localColumns}, rTo = Mapping {mTable = name, mColumns = localColumns, mConstraintName = Just constraintName},
rFrom = Mapping {mTable = relatedTable, mColumns = relatedColumns} rFrom = Mapping {mTable = relatedTable, mColumns = relatedColumns, mConstraintName = Nothing}
} }
] ]
where where
@ -154,16 +160,18 @@ suggestRelsFK omitTracked tables name uniqueConstraints tracked foreignKey =
relatedColumns = Map.elems columnRelationships relatedColumns = Map.elems columnRelationships
uniqueConstraintColumns = H.map _ucColumns uniqueConstraints uniqueConstraintColumns = H.map _ucColumns uniqueConstraints
relatedTable = _fkForeignTable foreignKey relatedTable = _fkForeignTable foreignKey
constraintName = Aeson.toJSON (_cName (_fkConstraint foreignKey))
suggestRelsTable :: suggestRelsTable ::
forall b. forall b.
Backend b => Backend b =>
Bool -> Bool ->
HashMap (TableName b) (TableCoreInfo b) -> HashMap (TableName b) (TableCoreInfo b) ->
(TableName b -> Bool) ->
(TableName b, TableCoreInfo b) -> (TableName b, TableCoreInfo b) ->
[Relationship b] [Relationship b]
suggestRelsTable omitTracked tables (name, table) = suggestRelsTable omitTracked tables predicate (name, table) =
suggestRelsFK omitTracked tables name constraints tracked =<< toList foreignKeys suggestRelsFK omitTracked tables name constraints tracked predicate =<< toList foreignKeys
where where
foreignKeys = _tciForeignKeys table foreignKeys = _tciForeignKeys table
constraints = _tciUniqueConstraints table constraints = _tciUniqueConstraints table
@ -176,15 +184,17 @@ suggestRelsResponse ::
Backend b => Backend b =>
Bool -> Bool ->
HashMap (TableName b) (TableCoreInfo b) -> HashMap (TableName b) (TableCoreInfo b) ->
(TableName b -> Bool) ->
SuggestedRelationships b SuggestedRelationships b
suggestRelsResponse omitTracked tables = suggestRelsResponse omitTracked tables predicate =
Relationships $ Relationships $
suggestRelsTable omitTracked tables =<< Map.toList tables suggestRelsTable omitTracked tables predicate =<< Map.toList tables
-- | Helper to filter tables considered for relationships tablePredicate :: Hashable a => Maybe [a] -> a -> Bool
pluck :: Eq a => Maybe [a] -> Map.HashMap a b -> Map.HashMap a b tablePredicate Nothing _ = True
pluck Nothing = id tablePredicate (Just ns) n = n `H.member` hash
pluck (Just ks) = Map.mapMaybeWithKey (\k v -> if k `elem` ks then Just v else Nothing) where
hash = H.fromList ns
-- | The method invoked when dispatching on metadata calls in POST /v1/metadata -- | The method invoked when dispatching on metadata calls in POST /v1/metadata
runSuggestRels :: runSuggestRels ::
@ -196,4 +206,4 @@ runSuggestRels (SuggestRels source tablesM omitExistingB) = do
tableCacheM <- fmap (fmap (_tiCoreInfo)) <$> askTableCache @b source tableCacheM <- fmap (fmap (_tiCoreInfo)) <$> askTableCache @b source
case tableCacheM of case tableCacheM of
Nothing -> throw500 "Couldn't find any schema source information" Nothing -> throw500 "Couldn't find any schema source information"
Just tableCache -> pure $ encJFromJValue $ suggestRelsResponse @b omitExistingB (pluck tablesM tableCache) Just tableCache -> pure $ encJFromJValue $ suggestRelsResponse @b omitExistingB tableCache (tablePredicate tablesM)