2019-12-09 01:17:39 +03:00
|
|
|
|
{-# LANGUAGE Arrows #-}
|
|
|
|
|
|
|
|
|
|
module Hasura.RQL.DDL.Schema.Cache.Fields
|
|
|
|
|
( addNonColumnFields
|
|
|
|
|
, mkRelationshipMetadataObject
|
|
|
|
|
, mkComputedFieldMetadataObject
|
2020-05-27 18:02:58 +03:00
|
|
|
|
, mkRemoteRelationshipMetadataObject
|
2019-12-09 01:17:39 +03:00
|
|
|
|
) where
|
|
|
|
|
import Hasura.Prelude
|
|
|
|
|
|
|
|
|
|
import qualified Data.HashMap.Strict.Extended as M
|
2019-12-13 00:46:33 +03:00
|
|
|
|
import qualified Data.HashSet as HS
|
2019-12-09 01:17:39 +03:00
|
|
|
|
import qualified Data.Sequence as Seq
|
2019-12-13 10:47:28 +03:00
|
|
|
|
import qualified Language.GraphQL.Draft.Syntax as G
|
2020-05-27 18:02:58 +03:00
|
|
|
|
import qualified Hasura.GraphQL.Validate.Types as VT
|
2019-12-09 01:17:39 +03:00
|
|
|
|
|
|
|
|
|
import Control.Arrow.Extended
|
|
|
|
|
import Data.Aeson
|
|
|
|
|
|
|
|
|
|
import qualified Hasura.Incremental as Inc
|
|
|
|
|
|
|
|
|
|
import Hasura.RQL.DDL.ComputedField
|
|
|
|
|
import Hasura.RQL.DDL.Relationship
|
2020-05-27 18:02:58 +03:00
|
|
|
|
import Hasura.RQL.DDL.RemoteRelationship
|
2019-12-09 01:17:39 +03:00
|
|
|
|
import Hasura.RQL.DDL.Schema.Cache.Common
|
|
|
|
|
import Hasura.RQL.DDL.Schema.Function
|
|
|
|
|
import Hasura.RQL.Types
|
|
|
|
|
import Hasura.RQL.Types.Catalog
|
|
|
|
|
import Hasura.SQL.Types
|
|
|
|
|
|
|
|
|
|
addNonColumnFields
|
2019-12-11 04:46:34 +03:00
|
|
|
|
:: ( ArrowChoice arr, Inc.ArrowDistribute arr, ArrowWriter (Seq CollectedInfo) arr
|
2019-12-09 01:17:39 +03:00
|
|
|
|
, ArrowKleisli m arr, MonadError QErr m )
|
2019-12-09 07:18:53 +03:00
|
|
|
|
=> ( HashMap QualifiedTable TableRawInfo
|
2019-12-09 01:17:39 +03:00
|
|
|
|
, FieldInfoMap PGColumnInfo
|
2020-05-27 18:02:58 +03:00
|
|
|
|
, RemoteSchemaMap
|
2019-12-09 01:17:39 +03:00
|
|
|
|
, [CatalogRelation]
|
|
|
|
|
, [CatalogComputedField]
|
2020-05-27 18:02:58 +03:00
|
|
|
|
, [RemoteRelationship]
|
|
|
|
|
) `arr` (FieldInfoMap FieldInfo, VT.TypeMap)
|
|
|
|
|
addNonColumnFields = proc (rawTableInfo, columns, remoteSchemaMap, relationships, computedFields, remoteRelationships) -> do
|
2020-01-31 02:55:09 +03:00
|
|
|
|
relationshipInfos
|
|
|
|
|
<- buildInfoMapPreservingMetadata _crRelName mkRelationshipMetadataObject buildRelationship
|
|
|
|
|
-< (_tciForeignKeys <$> rawTableInfo, relationships)
|
|
|
|
|
computedFieldInfos
|
|
|
|
|
<- buildInfoMapPreservingMetadata
|
|
|
|
|
(_afcName . _cccComputedField)
|
|
|
|
|
mkComputedFieldMetadataObject
|
|
|
|
|
buildComputedField
|
|
|
|
|
-< (HS.fromList $ M.keys rawTableInfo, computedFields)
|
2020-05-27 18:02:58 +03:00
|
|
|
|
rawRemoteRelationshipInfos
|
|
|
|
|
<- buildInfoMapPreservingMetadata rtrName mkRemoteRelationshipMetadataObject buildRemoteRelationship
|
|
|
|
|
-< ((M.elems columns, remoteSchemaMap), remoteRelationships)
|
2019-12-09 01:17:39 +03:00
|
|
|
|
|
2019-12-13 10:47:28 +03:00
|
|
|
|
let mapKey f = M.fromList . map (first f) . M.toList
|
2020-01-31 02:55:09 +03:00
|
|
|
|
relationshipFields = mapKey fromRel relationshipInfos
|
|
|
|
|
computedFieldFields = mapKey fromComputedField computedFieldInfos
|
2020-05-27 18:02:58 +03:00
|
|
|
|
remoteRelationshipFields = mapKey fromRemoteRelationship $
|
|
|
|
|
M.map (\((rf, _), mo) -> (rf, mo)) rawRemoteRelationshipInfos
|
|
|
|
|
typeMap = mconcat $ map (snd . fst) $ M.elems rawRemoteRelationshipInfos
|
2019-12-09 01:17:39 +03:00
|
|
|
|
|
2019-12-13 10:47:28 +03:00
|
|
|
|
-- First, check for conflicts between non-column fields, since we can raise a better error
|
|
|
|
|
-- message in terms of the two metadata objects that define them.
|
2020-05-27 18:02:58 +03:00
|
|
|
|
fieldInfoMap <- (align relationshipFields computedFieldFields >- returnA)
|
|
|
|
|
>-> (| Inc.keyed (\fieldName fields -> (fieldName, fields) >- noFieldConflicts FIRelationship FIComputedField) |)
|
|
|
|
|
-- Second, align with remote relationship fields
|
|
|
|
|
>-> (\fields -> align (M.catMaybes fields) remoteRelationshipFields >- returnA)
|
|
|
|
|
>-> (| Inc.keyed (\fieldName fields -> (fieldName, fields) >- noFieldConflicts id FIRemoteRelationship) |)
|
2019-12-13 10:47:28 +03:00
|
|
|
|
-- Next, check for conflicts with custom field names. This is easiest to do before merging with
|
|
|
|
|
-- the column info itself because we have access to the information separately, and custom field
|
|
|
|
|
-- names are not currently stored as a separate map (but maybe should be!).
|
|
|
|
|
>-> (\fields -> (columns, M.catMaybes fields) >- noCustomFieldConflicts)
|
|
|
|
|
-- Finally, check for conflicts with the columns themselves.
|
|
|
|
|
>-> (\fields -> align columns (M.catMaybes fields) >- returnA)
|
|
|
|
|
>-> (| Inc.keyed (\_ fields -> fields >- noColumnConflicts) |)
|
2020-05-27 18:02:58 +03:00
|
|
|
|
|
|
|
|
|
returnA -< (fieldInfoMap, typeMap)
|
2019-12-09 01:17:39 +03:00
|
|
|
|
where
|
2020-05-27 18:02:58 +03:00
|
|
|
|
noFieldConflicts this that = proc (fieldName, fields) -> case fields of
|
|
|
|
|
This (thisField, metadata) -> returnA -< Just (this thisField, metadata)
|
|
|
|
|
That (thatField, metadata) -> returnA -< Just (that thatField, metadata)
|
|
|
|
|
These (_, thisMetadata) (_, thatMetadata) -> do
|
2019-12-09 01:17:39 +03:00
|
|
|
|
tellA -< Seq.singleton $ CIInconsistency $ ConflictingObjects
|
|
|
|
|
("conflicting definitions for field " <>> fieldName)
|
2020-05-27 18:02:58 +03:00
|
|
|
|
[thisMetadata, thatMetadata]
|
2019-12-09 01:17:39 +03:00
|
|
|
|
returnA -< Nothing
|
|
|
|
|
|
2019-12-13 10:47:28 +03:00
|
|
|
|
noCustomFieldConflicts = proc (columns, nonColumnFields) -> do
|
|
|
|
|
let columnsByGQLName = mapFromL pgiName $ M.elems columns
|
|
|
|
|
(| Inc.keyed (\_ (fieldInfo, metadata) ->
|
|
|
|
|
(| withRecordInconsistency (do
|
|
|
|
|
(| traverseA_ (\fieldGQLName -> case M.lookup fieldGQLName columnsByGQLName of
|
|
|
|
|
-- Only raise an error if the GQL name isn’t the same as the Postgres column name.
|
|
|
|
|
-- If they are the same, `noColumnConflicts` will catch it, and it will produce a
|
|
|
|
|
-- more useful error message.
|
|
|
|
|
Just columnInfo | getPGColTxt (pgiColumn columnInfo) /= G.unName fieldGQLName ->
|
|
|
|
|
throwA -< err400 AlreadyExists
|
|
|
|
|
$ "field definition conflicts with custom field name for postgres column "
|
|
|
|
|
<>> pgiColumn columnInfo
|
|
|
|
|
_ -> returnA -< ())
|
|
|
|
|
|) (fieldInfoGraphQLNames fieldInfo)
|
|
|
|
|
returnA -< (fieldInfo, metadata))
|
|
|
|
|
|) metadata)
|
|
|
|
|
|) nonColumnFields
|
|
|
|
|
|
2019-12-09 01:17:39 +03:00
|
|
|
|
noColumnConflicts = proc fields -> case fields of
|
|
|
|
|
This columnInfo -> returnA -< FIColumn columnInfo
|
|
|
|
|
That (fieldInfo, _) -> returnA -< fieldInfo
|
|
|
|
|
These columnInfo (_, fieldMetadata) -> do
|
|
|
|
|
recordInconsistency -< (fieldMetadata, "field definition conflicts with postgres column")
|
|
|
|
|
returnA -< FIColumn columnInfo
|
|
|
|
|
|
|
|
|
|
mkRelationshipMetadataObject :: CatalogRelation -> MetadataObject
|
|
|
|
|
mkRelationshipMetadataObject (CatalogRelation qt rn rt rDef cmnt) =
|
|
|
|
|
let objectId = MOTableObj qt $ MTORel rn rt
|
|
|
|
|
definition = toJSON $ WithTable qt $ RelDef rn rDef cmnt
|
|
|
|
|
in MetadataObject objectId definition
|
|
|
|
|
|
|
|
|
|
buildRelationship
|
2019-12-11 04:46:34 +03:00
|
|
|
|
:: (ArrowChoice arr, ArrowWriter (Seq CollectedInfo) arr)
|
2019-12-13 00:46:33 +03:00
|
|
|
|
=> (HashMap QualifiedTable (HashSet ForeignKey), CatalogRelation) `arr` Maybe RelInfo
|
2019-12-09 01:17:39 +03:00
|
|
|
|
buildRelationship = proc (foreignKeys, relationship) -> do
|
|
|
|
|
let CatalogRelation tableName rn rt rDef _ = relationship
|
|
|
|
|
metadataObject = mkRelationshipMetadataObject relationship
|
|
|
|
|
schemaObject = SOTableObj tableName $ TORel rn
|
|
|
|
|
addRelationshipContext e = "in relationship " <> rn <<> ": " <> e
|
|
|
|
|
(| withRecordInconsistency (
|
|
|
|
|
(| modifyErrA (do
|
2019-12-09 07:18:53 +03:00
|
|
|
|
(info, dependencies) <- liftEitherA -< case rt of
|
2019-12-09 01:17:39 +03:00
|
|
|
|
ObjRel -> do
|
|
|
|
|
using <- decodeValue rDef
|
2019-12-09 07:18:53 +03:00
|
|
|
|
tableForeignKeys <- getTableInfo tableName foreignKeys
|
|
|
|
|
objRelP2Setup tableName tableForeignKeys (RelDef rn using Nothing)
|
2019-12-09 01:17:39 +03:00
|
|
|
|
ArrRel -> do
|
|
|
|
|
using <- decodeValue rDef
|
2019-12-09 07:18:53 +03:00
|
|
|
|
arrRelP2Setup foreignKeys tableName (RelDef rn using Nothing)
|
2019-12-09 01:17:39 +03:00
|
|
|
|
recordDependencies -< (metadataObject, schemaObject, dependencies)
|
|
|
|
|
returnA -< info)
|
|
|
|
|
|) (addTableContext tableName . addRelationshipContext))
|
|
|
|
|
|) metadataObject
|
|
|
|
|
|
|
|
|
|
mkComputedFieldMetadataObject :: CatalogComputedField -> MetadataObject
|
|
|
|
|
mkComputedFieldMetadataObject (CatalogComputedField column _) =
|
|
|
|
|
let AddComputedField qt name _ _ = column
|
|
|
|
|
objectId = MOTableObj qt $ MTOComputedField name
|
|
|
|
|
in MetadataObject objectId (toJSON column)
|
|
|
|
|
|
|
|
|
|
buildComputedField
|
2019-12-11 04:46:34 +03:00
|
|
|
|
:: ( ArrowChoice arr, ArrowWriter (Seq CollectedInfo) arr
|
2019-12-09 01:17:39 +03:00
|
|
|
|
, ArrowKleisli m arr, MonadError QErr m )
|
|
|
|
|
=> (HashSet QualifiedTable, CatalogComputedField) `arr` Maybe ComputedFieldInfo
|
|
|
|
|
buildComputedField = proc (trackedTableNames, computedField) -> do
|
|
|
|
|
let CatalogComputedField column funcDefs = computedField
|
|
|
|
|
AddComputedField qt name def comment = column
|
|
|
|
|
addComputedFieldContext e = "in computed field " <> name <<> ": " <> e
|
|
|
|
|
(| withRecordInconsistency (
|
|
|
|
|
(| modifyErrA (do
|
|
|
|
|
rawfi <- bindErrorA -< handleMultipleFunctions (_cfdFunction def) funcDefs
|
|
|
|
|
bindErrorA -< addComputedFieldP2Setup trackedTableNames qt name def rawfi comment)
|
|
|
|
|
|) (addTableContext qt . addComputedFieldContext))
|
|
|
|
|
|) (mkComputedFieldMetadataObject computedField)
|
2020-05-27 18:02:58 +03:00
|
|
|
|
|
|
|
|
|
mkRemoteRelationshipMetadataObject :: RemoteRelationship -> MetadataObject
|
|
|
|
|
mkRemoteRelationshipMetadataObject rr =
|
|
|
|
|
let objectId = MOTableObj (rtrTable rr) $ MTORemoteRelationship $ rtrName rr
|
|
|
|
|
in MetadataObject objectId $ toJSON rr
|
|
|
|
|
|
|
|
|
|
buildRemoteRelationship
|
|
|
|
|
:: ( ArrowChoice arr, ArrowWriter (Seq CollectedInfo) arr
|
|
|
|
|
, ArrowKleisli m arr, MonadError QErr m )
|
|
|
|
|
=> (([PGColumnInfo], RemoteSchemaMap), RemoteRelationship) `arr` Maybe (RemoteFieldInfo, VT.TypeMap)
|
|
|
|
|
buildRemoteRelationship = proc ((pgColumns, remoteSchemaMap), remoteRelationship) -> do
|
|
|
|
|
let relationshipName = rtrName remoteRelationship
|
|
|
|
|
tableName = rtrTable remoteRelationship
|
|
|
|
|
metadataObject = mkRemoteRelationshipMetadataObject remoteRelationship
|
|
|
|
|
schemaObj = SOTableObj (rtrTable remoteRelationship) $ TORemoteRel relationshipName
|
|
|
|
|
addRemoteRelationshipContext e = "in remote relationship" <> relationshipName <<> ": " <> e
|
|
|
|
|
(| withRecordInconsistency (
|
|
|
|
|
(| modifyErrA (do
|
|
|
|
|
(remoteField, typeMap, dependencies) <- bindErrorA -< resolveRemoteRelationship remoteRelationship pgColumns remoteSchemaMap
|
|
|
|
|
recordDependencies -< (metadataObject, schemaObj, dependencies)
|
|
|
|
|
returnA -< (remoteField, typeMap))
|
|
|
|
|
|)(addTableContext tableName . addRemoteRelationshipContext))
|
|
|
|
|
|) metadataObject
|