mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-19 06:28:39 +03:00
chore(server): more expressive logical models
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9197 GitOrigin-RevId: 3471c68d4e59310bdb62ecb3694ab30a2094916e
This commit is contained in:
parent
dcbe163a88
commit
a73a009031
@ -771,6 +771,28 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"BigqueryLogicalModelField": {
|
||||
"description": "A field of a logical model",
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Optional description of this field",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the field",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"additionalProperties": true,
|
||||
"description": "Type of the field\nA type used in a Logical Model field\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"BigqueryLogicalModelMetadata": {
|
||||
"description": "A return type.",
|
||||
"properties": {
|
||||
@ -782,7 +804,14 @@
|
||||
"description": "Return types for the logical model",
|
||||
"items": {
|
||||
"additionalProperties": true,
|
||||
"description": "A scalar type or reference to another logical model\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/BigqueryLogicalModelField"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/BigqueryLogicalModelField"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@ -1685,6 +1714,28 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CitusLogicalModelField": {
|
||||
"description": "A field of a logical model",
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Optional description of this field",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the field",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"additionalProperties": true,
|
||||
"description": "Type of the field\nA type used in a Logical Model field\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CitusLogicalModelMetadata": {
|
||||
"description": "A return type.",
|
||||
"properties": {
|
||||
@ -1696,7 +1747,14 @@
|
||||
"description": "Return types for the logical model",
|
||||
"items": {
|
||||
"additionalProperties": true,
|
||||
"description": "A scalar type or reference to another logical model\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/CitusLogicalModelField"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/CitusLogicalModelField"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@ -2643,6 +2701,28 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CockroachLogicalModelField": {
|
||||
"description": "A field of a logical model",
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Optional description of this field",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the field",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"additionalProperties": true,
|
||||
"description": "Type of the field\nA type used in a Logical Model field\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CockroachLogicalModelMetadata": {
|
||||
"description": "A return type.",
|
||||
"properties": {
|
||||
@ -2654,7 +2734,14 @@
|
||||
"description": "Return types for the logical model",
|
||||
"items": {
|
||||
"additionalProperties": true,
|
||||
"description": "A scalar type or reference to another logical model\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/CockroachLogicalModelField"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/CockroachLogicalModelField"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@ -3756,6 +3843,28 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DataconnectorLogicalModelField": {
|
||||
"description": "A field of a logical model",
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Optional description of this field",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the field",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"additionalProperties": true,
|
||||
"description": "Type of the field\nA type used in a Logical Model field\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DataconnectorLogicalModelMetadata": {
|
||||
"description": "A return type.",
|
||||
"properties": {
|
||||
@ -3767,7 +3876,14 @@
|
||||
"description": "Return types for the logical model",
|
||||
"items": {
|
||||
"additionalProperties": true,
|
||||
"description": "A scalar type or reference to another logical model\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DataconnectorLogicalModelField"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DataconnectorLogicalModelField"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@ -5391,6 +5507,28 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MssqlLogicalModelField": {
|
||||
"description": "A field of a logical model",
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Optional description of this field",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the field",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"additionalProperties": true,
|
||||
"description": "Type of the field\nA type used in a Logical Model field\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MssqlLogicalModelMetadata": {
|
||||
"description": "A return type.",
|
||||
"properties": {
|
||||
@ -5402,7 +5540,14 @@
|
||||
"description": "Return types for the logical model",
|
||||
"items": {
|
||||
"additionalProperties": true,
|
||||
"description": "A scalar type or reference to another logical model\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/MssqlLogicalModelField"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/MssqlLogicalModelField"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
@ -6623,6 +6768,28 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PostgresLogicalModelField": {
|
||||
"description": "A field of a logical model",
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Optional description of this field",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the field",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"additionalProperties": true,
|
||||
"description": "Type of the field\nA type used in a Logical Model field\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PostgresLogicalModelMetadata": {
|
||||
"description": "A return type.",
|
||||
"properties": {
|
||||
@ -6634,7 +6801,14 @@
|
||||
"description": "Return types for the logical model",
|
||||
"items": {
|
||||
"additionalProperties": true,
|
||||
"description": "A scalar type or reference to another logical model\nvalue with unspecified type - this is a placeholder that will eventually be replaced with a more detailed description"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/PostgresLogicalModelField"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/PostgresLogicalModelField"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
|
@ -97,9 +97,10 @@ testImplementation = do
|
||||
description: hello
|
||||
fields:
|
||||
- name: divided
|
||||
type: #{scalarTypeToText testEnvironment Schema.TInt}
|
||||
nullable: false
|
||||
description: "a divided thing"
|
||||
type:
|
||||
scalar: #{scalarTypeToText testEnvironment Schema.TInt}
|
||||
nullable: false
|
||||
|]
|
||||
|
||||
it "Checks the logical model is deleted again" $ \testEnvironment -> do
|
||||
@ -188,8 +189,9 @@ testPermissions = do
|
||||
fields:
|
||||
- description: a divided thing
|
||||
name: divided
|
||||
nullable: false
|
||||
type: integer
|
||||
type:
|
||||
nullable: false
|
||||
scalar: integer
|
||||
select_permissions:
|
||||
- role: "test"
|
||||
permission:
|
||||
|
@ -35,6 +35,10 @@ instance J.ToJSON ReferenceType where
|
||||
toJSON ArrayReference = "array"
|
||||
toJSON ObjectReference = "object"
|
||||
|
||||
-- | this no longer matches the internal shape of logical models, where arrays
|
||||
-- can nest objects OR scalars
|
||||
-- however, we can defer changing this abstraction until we need to express
|
||||
-- that in our tests
|
||||
data LogicalModelColumn
|
||||
= LogicalModelScalar
|
||||
{ logicalModelColumnName :: Text,
|
||||
@ -91,23 +95,49 @@ logicalModel logicalModelName =
|
||||
|
||||
trackLogicalModelCommand :: String -> BackendTypeConfig -> LogicalModel -> Value
|
||||
trackLogicalModelCommand sourceName backendTypeConfig (LogicalModel {logicalModelDescription, logicalModelName, logicalModelColumns}) =
|
||||
-- return type is an array of items
|
||||
let returnTypeToJson =
|
||||
J.Array
|
||||
. V.fromList
|
||||
. fmap
|
||||
( \case
|
||||
LogicalModelReference {..} ->
|
||||
J.object $
|
||||
[ ("logical_model" .= logicalModelColumnReference),
|
||||
("link_type" .= logicalModelColumnReferenceType),
|
||||
("name" .= logicalModelColumnName)
|
||||
]
|
||||
LogicalModelReference
|
||||
{ logicalModelColumnReferenceType = ObjectReference,
|
||||
logicalModelColumnReference,
|
||||
logicalModelColumnName
|
||||
} ->
|
||||
J.object $
|
||||
[ ("name" .= logicalModelColumnName),
|
||||
( "type",
|
||||
J.object
|
||||
[ ("logical_model" .= logicalModelColumnReference)
|
||||
]
|
||||
)
|
||||
]
|
||||
LogicalModelReference
|
||||
{ logicalModelColumnReferenceType = ArrayReference,
|
||||
logicalModelColumnReference,
|
||||
logicalModelColumnName
|
||||
} ->
|
||||
J.object $
|
||||
[ ("name" .= logicalModelColumnName),
|
||||
( "type",
|
||||
J.object $
|
||||
[ ( "array",
|
||||
J.object $
|
||||
[ ("logical_model" .= logicalModelColumnReference)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
LogicalModelScalar {..} ->
|
||||
let descriptionPair = case logicalModelColumnDescription of
|
||||
Just desc -> [("description" .= desc)]
|
||||
Nothing -> []
|
||||
in J.object $
|
||||
in -- this is the old way to encode these, but we'll keep using
|
||||
-- in the tests for now to ensure we remain backwards
|
||||
-- compatible
|
||||
J.object $
|
||||
[ ("name" .= logicalModelColumnName),
|
||||
("type" .= (BackendType.backendScalarType backendTypeConfig) logicalModelColumnType),
|
||||
("nullable" .= logicalModelColumnNullable)
|
||||
|
@ -26,6 +26,8 @@ module Hasura.GraphQL.Schema.Select
|
||||
tablePermissionsInfo,
|
||||
tableSelectionList,
|
||||
logicalModelSelectionList,
|
||||
logicalModelArrayRelationshipField,
|
||||
logicalModelObjectRelationshipField,
|
||||
)
|
||||
where
|
||||
|
||||
@ -63,8 +65,16 @@ import Hasura.GraphQL.Schema.Parser qualified as P
|
||||
import Hasura.GraphQL.Schema.Table
|
||||
import Hasura.GraphQL.Schema.Typename
|
||||
import Hasura.LogicalModel.Cache (LogicalModelInfo (..))
|
||||
import Hasura.LogicalModel.Types (LogicalModelField (..), LogicalModelName (..), LogicalModelReferenceType (..))
|
||||
import Hasura.LogicalModel.Types
|
||||
( LogicalModelField (..),
|
||||
LogicalModelName (..),
|
||||
LogicalModelType (..),
|
||||
LogicalModelTypeArray (..),
|
||||
LogicalModelTypeReference (..),
|
||||
LogicalModelTypeScalar (..),
|
||||
)
|
||||
import Hasura.Name qualified as Name
|
||||
import Hasura.NativeQuery.Cache (NativeQueryInfo (..))
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR qualified as IR
|
||||
import Hasura.RQL.IR.BoolExp
|
||||
@ -495,10 +505,95 @@ columnToRelName :: forall b. (Backend b) => Column b -> Maybe RelName
|
||||
columnToRelName column =
|
||||
RelName <$> mkNonEmptyText (toTxt column)
|
||||
|
||||
-- | parse a single logical model field. Currently the only way to 'fulfil' a
|
||||
-- non-scalar field is with a relationship that provides the nested
|
||||
-- object/array information.
|
||||
parseLogicalModelField ::
|
||||
forall b r m n.
|
||||
( MonadBuildSchema b r m n,
|
||||
BackendNativeQuerySelectSchema b
|
||||
) =>
|
||||
InsOrdHashMap RelName (RelInfo b) ->
|
||||
Column b ->
|
||||
LogicalModelField b ->
|
||||
MaybeT (SchemaT r m) (IP.FieldParser MetadataObjId n (AnnotatedField b))
|
||||
parseLogicalModelField
|
||||
_
|
||||
column
|
||||
( LogicalModelField
|
||||
{ lmfDescription,
|
||||
lmfType = LogicalModelTypeScalar (LogicalModelTypeScalarC {lmtsScalar, lmtsNullable})
|
||||
}
|
||||
) = do
|
||||
columnName <- hoistMaybe (G.mkName (toTxt column))
|
||||
|
||||
-- We have not yet worked out what providing permissions here enables
|
||||
let caseBoolExpUnpreparedValue = Nothing
|
||||
columnType = ColumnScalar lmtsScalar
|
||||
pathArg = scalarSelectionArgumentsParser columnType
|
||||
|
||||
field <- lift $ columnParser columnType (G.Nullability lmtsNullable)
|
||||
|
||||
pure $!
|
||||
P.selection columnName (G.Description <$> lmfDescription) pathArg field
|
||||
<&> IR.mkAnnColumnField column columnType caseBoolExpUnpreparedValue
|
||||
parseLogicalModelField
|
||||
relationshipInfo
|
||||
column
|
||||
( LogicalModelField
|
||||
{ lmfType =
|
||||
LogicalModelTypeReference
|
||||
(LogicalModelTypeReferenceC {lmtrReference})
|
||||
}
|
||||
) = do
|
||||
-- we currently ignore nullability and assume the field is nullable
|
||||
relName <- hoistMaybe $ columnToRelName @b column
|
||||
-- lookup the reference in the data source
|
||||
relationship <- hoistMaybe $ InsOrdHashMap.lookup relName relationshipInfo
|
||||
logicalModelObjectRelationshipField @b @r @m @n lmtrReference relationship
|
||||
parseLogicalModelField
|
||||
relationshipInfo
|
||||
column
|
||||
( LogicalModelField
|
||||
{ lmfType =
|
||||
LogicalModelTypeArray
|
||||
( LogicalModelTypeArrayC
|
||||
{ lmtaArray =
|
||||
LogicalModelTypeReference (LogicalModelTypeReferenceC {lmtrReference})
|
||||
}
|
||||
)
|
||||
}
|
||||
) = do
|
||||
-- we currently ignore nullability and assume the field is
|
||||
-- non-nullable, as are the contents
|
||||
relName <- hoistMaybe $ columnToRelName @b column
|
||||
-- lookup the reference in the data source
|
||||
relationship <- hoistMaybe $ InsOrdHashMap.lookup relName relationshipInfo
|
||||
logicalModelArrayRelationshipField @b @r @m @n lmtrReference relationship
|
||||
parseLogicalModelField
|
||||
_
|
||||
_
|
||||
( LogicalModelField
|
||||
{ lmfType =
|
||||
LogicalModelTypeArray
|
||||
(LogicalModelTypeArrayC {lmtaArray = LogicalModelTypeScalar _})
|
||||
}
|
||||
) =
|
||||
throw500 "Arrays of scalar types are not currently implemented"
|
||||
parseLogicalModelField
|
||||
_
|
||||
_
|
||||
( LogicalModelField
|
||||
{ lmfType =
|
||||
LogicalModelTypeArray
|
||||
(LogicalModelTypeArrayC {lmtaArray = LogicalModelTypeArray _})
|
||||
}
|
||||
) =
|
||||
throw500 "Nested arrays are not currently implemented"
|
||||
|
||||
defaultLogicalModelSelectionSet ::
|
||||
forall b r m n.
|
||||
( MonadBuildSchema b r m n,
|
||||
BackendTableSelectSchema b,
|
||||
BackendNativeQuerySelectSchema b
|
||||
) =>
|
||||
InsOrdHashMap RelName (RelInfo b) ->
|
||||
@ -514,37 +609,6 @@ defaultLogicalModelSelectionSet relationshipInfo logicalModel = runMaybeT $ do
|
||||
Permission.PCStar -> True
|
||||
Permission.PCCols cols -> column `elem` cols
|
||||
|
||||
let parseField ::
|
||||
Column b ->
|
||||
LogicalModelField b ->
|
||||
MaybeT (SchemaT r m) (IP.FieldParser MetadataObjId n (AnnotatedField b))
|
||||
parseField column inputField = do
|
||||
columnName <- hoistMaybe (G.mkName (toTxt column))
|
||||
|
||||
-- We have not yet worked out what providing permissions here enables
|
||||
let caseBoolExpUnpreparedValue = Nothing
|
||||
|
||||
case inputField of
|
||||
LogicalModelScalarField {..} -> do
|
||||
let columnType = ColumnScalar lmfType
|
||||
pathArg = scalarSelectionArgumentsParser columnType
|
||||
|
||||
field <- lift $ columnParser columnType (G.Nullability lmfNullable)
|
||||
|
||||
pure $!
|
||||
P.selection columnName (G.Description <$> lmfDescription) pathArg field
|
||||
<&> IR.mkAnnColumnField column columnType caseBoolExpUnpreparedValue
|
||||
LogicalModelReference {..} -> do
|
||||
relName <- hoistMaybe $ columnToRelName @b column
|
||||
-- fetch the nested custom return type for comparison purposes
|
||||
_nestedLogicalModel <- lift $ askLogicalModelInfo @b lmfLogicalModel
|
||||
-- lookup the reference in the data source
|
||||
relationship <- hoistMaybe $ InsOrdHashMap.lookup relName relationshipInfo
|
||||
-- check the types match
|
||||
-- return IR for the actual data source lookup (ie, the table
|
||||
-- lookup for a relationship)
|
||||
logicalModelRelationshipField @b @r @m @n lmfReferenceType relationship
|
||||
|
||||
let fieldName = getLogicalModelName (_lmiName logicalModel)
|
||||
|
||||
-- which columns are we allowed to access given permissions?
|
||||
@ -553,7 +617,7 @@ defaultLogicalModelSelectionSet relationshipInfo logicalModel = runMaybeT $ do
|
||||
(isSelectable . fst)
|
||||
(InsOrdHashMap.toList (_lmiFields logicalModel))
|
||||
|
||||
parsers <- traverse (uncurry parseField) allowedColumns
|
||||
parsers <- traverse (uncurry (parseLogicalModelField relationshipInfo)) allowedColumns
|
||||
|
||||
let description = G.Description <$> _lmiDescription logicalModel
|
||||
|
||||
@ -1612,57 +1676,83 @@ tablePermissionsInfo selectPermissions =
|
||||
IR._tpLimit = spiLimit selectPermissions
|
||||
}
|
||||
|
||||
-- | Field parsers for a logical model relationship
|
||||
logicalModelRelationshipField ::
|
||||
-- | Field parsers for a logical model object relationship
|
||||
logicalModelObjectRelationshipField ::
|
||||
forall b r m n.
|
||||
( BackendTableSelectSchema b,
|
||||
BackendNativeQuerySelectSchema b,
|
||||
( BackendNativeQuerySelectSchema b,
|
||||
MonadBuildSchema b r m n
|
||||
) =>
|
||||
LogicalModelReferenceType ->
|
||||
LogicalModelName ->
|
||||
RelInfo b ->
|
||||
MaybeT (SchemaT r m) (FieldParser n (AnnotatedField b))
|
||||
logicalModelRelationshipField relationshipType ri =
|
||||
case (relationshipType, riType ri) of
|
||||
(ObjectReference, ObjRel) ->
|
||||
case riTarget ri of
|
||||
RelTargetNativeQuery nativeQueryName -> do
|
||||
nativeQueryInfo <- lift $ askNativeQueryInfo nativeQueryName
|
||||
relFieldName <- lift $ textToName $ relNameToTxt $ riName ri
|
||||
logicalModelObjectRelationshipField logicalModelName ri | riType ri == ObjRel =
|
||||
case riTarget ri of
|
||||
RelTargetNativeQuery nativeQueryName -> do
|
||||
nativeQueryInfo <- lift $ askNativeQueryInfo nativeQueryName
|
||||
|
||||
let objectRelDesc = Just $ G.Description "An object relationship"
|
||||
-- not sure if this the correct way to report mismatches, or if it
|
||||
-- even possible for this to be an issue at this point
|
||||
when
|
||||
(logicalModelName /= _lmiName (_nqiReturns nativeQueryInfo))
|
||||
( throw500 $
|
||||
"Expected object relationship to return "
|
||||
<> toTxt logicalModelName
|
||||
<> " but it returns "
|
||||
<> toTxt (_lmiName (_nqiReturns nativeQueryInfo))
|
||||
<> "."
|
||||
)
|
||||
|
||||
nativeQueryParser <- MaybeT $ selectNativeQueryObject nativeQueryInfo relFieldName objectRelDesc
|
||||
relFieldName <- lift $ textToName $ relNameToTxt $ riName ri
|
||||
|
||||
pure $
|
||||
nativeQueryParser <&> \selectExp ->
|
||||
IR.AFObjectRelation (IR.AnnRelationSelectG (riName ri) (riMapping ri) selectExp)
|
||||
RelTargetTable _otherTableName -> do
|
||||
throw500 "Object relationships from logical models to tables are not implemented"
|
||||
(ArrayReference, ArrRel) ->
|
||||
case riTarget ri of
|
||||
RelTargetNativeQuery nativeQueryName -> do
|
||||
nativeQueryInfo <- lift $ askNativeQueryInfo nativeQueryName
|
||||
relFieldName <- lift $ textToName $ relNameToTxt $ riName ri
|
||||
let objectRelDesc = Just $ G.Description "An object relationship"
|
||||
|
||||
let objectRelDesc = Just $ G.Description "An array relationship"
|
||||
nativeQueryParser <- MaybeT $ selectNativeQueryObject nativeQueryInfo relFieldName objectRelDesc
|
||||
|
||||
nativeQueryParser <- MaybeT $ selectNativeQuery nativeQueryInfo relFieldName objectRelDesc
|
||||
pure $
|
||||
nativeQueryParser <&> \selectExp ->
|
||||
IR.AFObjectRelation (IR.AnnRelationSelectG (riName ri) (riMapping ri) selectExp)
|
||||
RelTargetTable _otherTableName -> do
|
||||
throw500 "Object relationships from logical models to tables are not implemented"
|
||||
logicalModelObjectRelationshipField _ _ =
|
||||
hoistMaybe Nothing -- the target logical model expected an object relationship, but this was an array
|
||||
|
||||
pure $
|
||||
nativeQueryParser <&> \selectExp ->
|
||||
IR.AFArrayRelation $
|
||||
IR.ASSimple $
|
||||
IR.AnnRelationSelectG (riName ri) (riMapping ri) selectExp
|
||||
RelTargetTable otherTableName -> do
|
||||
otherTableInfo <- lift $ askTableInfo otherTableName
|
||||
relFieldName <- lift $ textToName $ relNameToTxt $ riName ri
|
||||
-- | Field parsers for a logical model relationship
|
||||
logicalModelArrayRelationshipField ::
|
||||
forall b r m n.
|
||||
( BackendNativeQuerySelectSchema b,
|
||||
MonadBuildSchema b r m n
|
||||
) =>
|
||||
LogicalModelName ->
|
||||
RelInfo b ->
|
||||
MaybeT (SchemaT r m) (FieldParser n (AnnotatedField b))
|
||||
logicalModelArrayRelationshipField logicalModelName ri | riType ri == ArrRel =
|
||||
case riTarget ri of
|
||||
RelTargetNativeQuery nativeQueryName -> do
|
||||
nativeQueryInfo <- lift $ askNativeQueryInfo nativeQueryName
|
||||
relFieldName <- lift $ textToName $ relNameToTxt $ riName ri
|
||||
|
||||
let arrayRelDesc = Just $ G.Description "An array relationship"
|
||||
otherTableParser <- MaybeT $ selectTable otherTableInfo relFieldName arrayRelDesc
|
||||
pure $
|
||||
otherTableParser <&> \selectExp ->
|
||||
IR.AFArrayRelation $
|
||||
IR.ASSimple $
|
||||
IR.AnnRelationSelectG (riName ri) (riMapping ri) selectExp
|
||||
_ -> hoistMaybe Nothing -- mismatch between relationship type expected on Logical Model, and in the source of data
|
||||
-- not sure if this the correct way to report mismatches, or if it
|
||||
-- even possible for this to be an issue at this point
|
||||
when
|
||||
(logicalModelName /= _lmiName (_nqiReturns nativeQueryInfo))
|
||||
( throw500 $
|
||||
"Expected array relationship to return "
|
||||
<> toTxt logicalModelName
|
||||
<> " but it returns "
|
||||
<> toTxt (_lmiName (_nqiReturns nativeQueryInfo))
|
||||
<> "."
|
||||
)
|
||||
|
||||
let objectRelDesc = Just $ G.Description "An array relationship"
|
||||
|
||||
nativeQueryParser <- MaybeT $ selectNativeQuery nativeQueryInfo relFieldName objectRelDesc
|
||||
|
||||
pure $
|
||||
nativeQueryParser <&> \selectExp ->
|
||||
IR.AFArrayRelation $
|
||||
IR.ASSimple $
|
||||
IR.AnnRelationSelectG (riName ri) (riMapping ri) selectExp
|
||||
RelTargetTable _otherTableName -> do
|
||||
throw500 "Array relationships from logical models to tables are not implemented"
|
||||
logicalModelArrayRelationshipField _ _ =
|
||||
hoistMaybe Nothing -- the target logical model expected an array relationship, but this was an object
|
||||
|
@ -10,7 +10,7 @@ import Data.HashMap.Strict qualified as HashMap
|
||||
import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
|
||||
import Data.Text.Extended (ToTxt (toTxt))
|
||||
import Hasura.LogicalModel.NullableScalarType (NullableScalarType (..))
|
||||
import Hasura.LogicalModel.Types (LogicalModelField (..))
|
||||
import Hasura.LogicalModel.Types (LogicalModelField (..), LogicalModelType (..), LogicalModelTypeScalar (..))
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.Types.Backend (Backend (..))
|
||||
import Hasura.RQL.Types.Column (ColumnInfo (..), ColumnMutability (..), ColumnType (..), fromCol)
|
||||
@ -23,9 +23,14 @@ columnsFromFields ::
|
||||
columnsFromFields =
|
||||
InsOrdHashMap.mapMaybe
|
||||
( \case
|
||||
LogicalModelScalarField
|
||||
{ lmfType = nstType,
|
||||
lmfNullable = nstNullable,
|
||||
LogicalModelField
|
||||
{ lmfType =
|
||||
LogicalModelTypeScalar
|
||||
( LogicalModelTypeScalarC
|
||||
{ lmtsScalar = nstType,
|
||||
lmtsNullable = nstNullable
|
||||
}
|
||||
),
|
||||
lmfDescription = nstDescription
|
||||
} ->
|
||||
Just (NullableScalarType {..})
|
||||
|
@ -1,8 +1,13 @@
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
|
||||
-- | A name for a logical model as it is recognized by the graphql schema.
|
||||
module Hasura.LogicalModel.Types
|
||||
( LogicalModelName (..),
|
||||
LogicalModelReferenceType (..),
|
||||
LogicalModelField (..),
|
||||
LogicalModelType (..),
|
||||
LogicalModelTypeScalar (..),
|
||||
LogicalModelTypeArray (..),
|
||||
LogicalModelTypeReference (..),
|
||||
logicalModelFieldMapCodec,
|
||||
)
|
||||
where
|
||||
@ -12,12 +17,13 @@ import Autodocodec
|
||||
dimapCodec,
|
||||
)
|
||||
import Autodocodec qualified as AC
|
||||
import Data.Aeson (FromJSON (..), FromJSONKey, ToJSON (..), ToJSONKey, Value, object, withObject, (.!=), (.:), (.:?), (.=))
|
||||
import Data.Aeson (FromJSON (..), FromJSONKey, ToJSON (..), ToJSONKey, Value)
|
||||
import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
|
||||
import Data.Text.Extended (ToTxt)
|
||||
import Hasura.Metadata.DTO.Placeholder (placeholderCodecViaJSON)
|
||||
import Hasura.Prelude hiding (first)
|
||||
import Hasura.RQL.Types.Backend (Backend (..))
|
||||
import Hasura.RQL.Types.BackendTag (backendPrefix)
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
import Language.Haskell.TH.Syntax (Lift)
|
||||
|
||||
@ -35,77 +41,254 @@ instance ToJSONKey LogicalModelName
|
||||
|
||||
----
|
||||
|
||||
-- | Type of reference for another Logical Model - object or array
|
||||
data LogicalModelReferenceType
|
||||
= ArrayReference
|
||||
| ObjectReference
|
||||
deriving stock (Eq, Ord, Generic, Show)
|
||||
deriving (FromJSON, ToJSON) via (AC.Autodocodec LogicalModelReferenceType)
|
||||
data LogicalModelTypeScalar b = LogicalModelTypeScalarC
|
||||
{ lmtsScalar :: ScalarType b,
|
||||
lmtsNullable :: Bool
|
||||
}
|
||||
deriving (Generic)
|
||||
|
||||
instance HasCodec LogicalModelReferenceType where
|
||||
deriving stock instance (Backend b) => Eq (LogicalModelTypeScalar b)
|
||||
|
||||
deriving stock instance (Backend b) => Show (LogicalModelTypeScalar b)
|
||||
|
||||
instance (Backend b) => Hashable (LogicalModelTypeScalar b)
|
||||
|
||||
instance (Backend b) => NFData (LogicalModelTypeScalar b)
|
||||
|
||||
instance (Backend b) => HasCodec (LogicalModelTypeScalar b) where
|
||||
codec =
|
||||
AC.CommentCodec
|
||||
("Link type of reference, it refers to a single object or an array of objects")
|
||||
$ AC.stringConstCodec
|
||||
$ (ArrayReference, "array") :| [(ObjectReference, "object")]
|
||||
("A scalar type used in a Logical Model.")
|
||||
$ AC.object (backendPrefix @b <> "LogicalModelTypeScalar")
|
||||
$ LogicalModelTypeScalarC
|
||||
<$> AC.requiredField "scalar" scalarDoc
|
||||
AC..= lmtsScalar
|
||||
<*> AC.optionalFieldWithDefault "nullable" False nullableDoc
|
||||
AC..= lmtsNullable
|
||||
where
|
||||
scalarDoc = "Name of the scalar type"
|
||||
nullableDoc = "Whether this field is allowed to contain null values or not"
|
||||
|
||||
instance Hashable LogicalModelReferenceType
|
||||
deriving via
|
||||
(AC.Autodocodec (LogicalModelTypeScalar b))
|
||||
instance
|
||||
(Backend b) => (FromJSON (LogicalModelTypeScalar b))
|
||||
|
||||
instance NFData LogicalModelReferenceType
|
||||
deriving via
|
||||
(AC.Autodocodec (LogicalModelTypeScalar b))
|
||||
instance
|
||||
(Backend b) => (ToJSON (LogicalModelTypeScalar b))
|
||||
|
||||
----
|
||||
|
||||
-- | either a scalar type or a reference to another logical model
|
||||
data LogicalModelField b
|
||||
= LogicalModelScalarField
|
||||
{ lmfName :: Column b,
|
||||
lmfType :: ScalarType b, -- int, string, blah
|
||||
lmfNullable :: Bool,
|
||||
lmfDescription :: Maybe Text
|
||||
}
|
||||
| LogicalModelReference
|
||||
{ lmfName :: Column b,
|
||||
lmfLogicalModel :: LogicalModelName, -- name of another logical model
|
||||
lmfReferenceType :: LogicalModelReferenceType
|
||||
}
|
||||
data LogicalModelTypeArray b = LogicalModelTypeArrayC
|
||||
{ lmtaArray :: LogicalModelType b,
|
||||
lmtaNullable :: Bool
|
||||
}
|
||||
deriving (Generic)
|
||||
|
||||
instance (Backend b) => HasCodec (LogicalModelField b) where
|
||||
deriving stock instance (Backend b) => Eq (LogicalModelTypeArray b)
|
||||
|
||||
deriving stock instance (Backend b) => Show (LogicalModelTypeArray b)
|
||||
|
||||
instance (Backend b) => Hashable (LogicalModelTypeArray b)
|
||||
|
||||
instance (Backend b) => NFData (LogicalModelTypeArray b)
|
||||
|
||||
instance (Backend b) => HasCodec (LogicalModelTypeArray b) where
|
||||
codec =
|
||||
AC.CommentCodec
|
||||
("A scalar type or reference to another logical model")
|
||||
("An array type used in a Logical Model.")
|
||||
$ AC.object (backendPrefix @b <> "LogicalModelTypeArray")
|
||||
$ LogicalModelTypeArrayC
|
||||
<$> AC.requiredField "array" arrayDoc
|
||||
AC..= lmtaArray
|
||||
<*> AC.optionalFieldWithDefault "nullable" False nullableDoc
|
||||
AC..= lmtaNullable
|
||||
where
|
||||
arrayDoc = "Type of items inside array"
|
||||
nullableDoc = "Whether this field can be null or not"
|
||||
|
||||
deriving via
|
||||
(AC.Autodocodec (LogicalModelTypeArray b))
|
||||
instance
|
||||
(Backend b) => (FromJSON (LogicalModelTypeArray b))
|
||||
|
||||
deriving via
|
||||
(AC.Autodocodec (LogicalModelTypeArray b))
|
||||
instance
|
||||
(Backend b) => (ToJSON (LogicalModelTypeArray b))
|
||||
|
||||
----
|
||||
|
||||
data LogicalModelTypeReference = LogicalModelTypeReferenceC
|
||||
{ lmtrReference :: LogicalModelName,
|
||||
lmtrNullable :: Bool
|
||||
}
|
||||
deriving stock (Eq, Show, Generic)
|
||||
deriving anyclass (Hashable, NFData)
|
||||
|
||||
instance HasCodec LogicalModelTypeReference where
|
||||
codec =
|
||||
AC.CommentCodec
|
||||
("A reference to another Logical Model.")
|
||||
$ AC.object "LogicalModelTypeReference"
|
||||
$ LogicalModelTypeReferenceC
|
||||
<$> AC.requiredField "logical_model" referenceDoc
|
||||
AC..= lmtrReference
|
||||
<*> AC.optionalFieldWithDefault "nullable" False nullableDoc
|
||||
AC..= lmtrNullable
|
||||
where
|
||||
referenceDoc = "Name of another Logical Model to nest"
|
||||
nullableDoc = "Whether this field can be null or not"
|
||||
|
||||
deriving via
|
||||
(AC.Autodocodec LogicalModelTypeReference)
|
||||
instance
|
||||
(FromJSON LogicalModelTypeReference)
|
||||
|
||||
deriving via
|
||||
(AC.Autodocodec LogicalModelTypeReference)
|
||||
instance
|
||||
(ToJSON LogicalModelTypeReference)
|
||||
|
||||
----
|
||||
|
||||
data LogicalModelType b
|
||||
= LogicalModelTypeScalar (LogicalModelTypeScalar b)
|
||||
| LogicalModelTypeArray (LogicalModelTypeArray b)
|
||||
| LogicalModelTypeReference LogicalModelTypeReference
|
||||
deriving (Generic)
|
||||
|
||||
deriving stock instance (Backend b) => Eq (LogicalModelType b)
|
||||
|
||||
deriving stock instance (Backend b) => Show (LogicalModelType b)
|
||||
|
||||
instance (Backend b) => Hashable (LogicalModelType b)
|
||||
|
||||
instance (Backend b) => NFData (LogicalModelType b)
|
||||
|
||||
-- | forgive me, I really did try and do this the native Autodocodec way
|
||||
-- and everything I did kept freezing the whole of HGE
|
||||
instance (Backend b) => HasCodec (LogicalModelType b) where
|
||||
codec =
|
||||
AC.CommentCodec
|
||||
("A type used in a Logical Model field")
|
||||
$ placeholderCodecViaJSON
|
||||
|
||||
instance (Backend b) => FromJSON (LogicalModelField b) where
|
||||
parseJSON = withObject "LogicalModelField" \o ->
|
||||
parseReference o <|> parseScalar o
|
||||
where
|
||||
parseScalar obj = do
|
||||
lmfName <- obj .: "name"
|
||||
lmfType <- obj .: "type"
|
||||
lmfNullable <- obj .:? "nullable" .!= False
|
||||
lmfDescription <- obj .:? "description"
|
||||
pure (LogicalModelScalarField {..})
|
||||
parseReference obj = do
|
||||
lmfLogicalModel <- obj .: "logical_model"
|
||||
lmfReferenceType <- obj .: "link_type"
|
||||
lmfName <- obj .: "name"
|
||||
pure (LogicalModelReference {..})
|
||||
instance (Backend b) => FromJSON (LogicalModelType b) where
|
||||
parseJSON j =
|
||||
(LogicalModelTypeScalar <$> parseJSON j)
|
||||
<|> (LogicalModelTypeArray <$> parseJSON j)
|
||||
<|> (LogicalModelTypeReference <$> parseJSON j)
|
||||
|
||||
instance (Backend b) => ToJSON (LogicalModelField b) where
|
||||
toJSON (LogicalModelScalarField {..}) =
|
||||
object $
|
||||
[ "name" .= lmfName,
|
||||
"type" .= lmfType,
|
||||
"nullable" .= lmfNullable
|
||||
]
|
||||
<> maybeDescription
|
||||
instance (Backend b) => ToJSON (LogicalModelType b) where
|
||||
toJSON (LogicalModelTypeScalar t) = toJSON t
|
||||
toJSON (LogicalModelTypeArray t) = toJSON t
|
||||
toJSON (LogicalModelTypeReference t) = toJSON t
|
||||
|
||||
----
|
||||
|
||||
-- | a single field in a Logical Model
|
||||
data LogicalModelField b = LogicalModelField
|
||||
{ lmfName :: Column b,
|
||||
lmfType :: LogicalModelType b,
|
||||
lmfDescription :: Maybe Text
|
||||
}
|
||||
deriving (Generic)
|
||||
|
||||
data LogicalModelFieldSimple b = LogicalModelFieldSimple
|
||||
{ lmfsName :: Column b,
|
||||
lmfsScalar :: ScalarType b,
|
||||
lmfsNullable :: Bool,
|
||||
lmfsDescription :: Maybe Text
|
||||
}
|
||||
|
||||
-- | this codec is complicated because we want to support both the old scalar
|
||||
-- encoded fields and our new separate type
|
||||
instance (Backend b) => HasCodec (LogicalModelField b) where
|
||||
codec = AC.parseAlternative newCodec simpleCodecMappedToNew -- we always encode as `newCodec` but we try parsing with both
|
||||
where
|
||||
maybeDescription = case lmfDescription of
|
||||
Just desc -> ["description" .= desc]
|
||||
Nothing -> []
|
||||
toJSON (LogicalModelReference {..}) =
|
||||
object ["name" .= lmfName, "logical_model" .= lmfLogicalModel, "link_type" .= lmfReferenceType]
|
||||
-- if we parse the old kind, convert it to the new exciting kind
|
||||
fromSimple :: LogicalModelFieldSimple b -> LogicalModelField b
|
||||
fromSimple (LogicalModelFieldSimple {lmfsName, lmfsScalar, lmfsNullable, lmfsDescription}) =
|
||||
LogicalModelField
|
||||
{ lmfName = lmfsName,
|
||||
lmfDescription = lmfsDescription,
|
||||
lmfType =
|
||||
LogicalModelTypeScalar
|
||||
( LogicalModelTypeScalarC {lmtsScalar = lmfsScalar, lmtsNullable = lmfsNullable}
|
||||
)
|
||||
}
|
||||
|
||||
-- try and convert the new kind to the old (this is partial, but
|
||||
-- shouldn't actually be used)
|
||||
toSimple
|
||||
( LogicalModelField
|
||||
{ lmfName,
|
||||
lmfDescription,
|
||||
lmfType = LogicalModelTypeScalar (LogicalModelTypeScalarC {lmtsScalar, lmtsNullable})
|
||||
}
|
||||
) =
|
||||
LogicalModelFieldSimple
|
||||
{ lmfsName = lmfName,
|
||||
lmfsScalar = lmtsScalar,
|
||||
lmfsNullable = lmtsNullable,
|
||||
lmfsDescription = lmfDescription
|
||||
}
|
||||
toSimple _ = error "Could not convert LogicalModelField to LogicalModelFieldSimple"
|
||||
|
||||
simpleCodecMappedToNew :: AC.JSONCodec (LogicalModelField b)
|
||||
simpleCodecMappedToNew = AC.dimapCodec fromSimple toSimple simpleCodec
|
||||
|
||||
simpleCodec :: AC.JSONCodec (LogicalModelFieldSimple b)
|
||||
simpleCodec =
|
||||
-- this is the simpler old codec that did scalar types only
|
||||
AC.CommentCodec
|
||||
("A field of a logical model")
|
||||
$ AC.object (backendPrefix @b <> "LogicalModelField")
|
||||
$ LogicalModelFieldSimple
|
||||
<$> AC.requiredField "name" nameDoc
|
||||
AC..= lmfsName
|
||||
<*> AC.requiredField "type" typeDoc
|
||||
AC..= lmfsScalar
|
||||
<*> AC.optionalFieldWithDefault "nullable" False nullableDoc
|
||||
AC..= lmfsNullable
|
||||
<*> AC.optionalField "description" descriptionDoc
|
||||
AC..= lmfsDescription
|
||||
where
|
||||
nameDoc = "Name of the field"
|
||||
nullableDoc = "Is field nullable or not?"
|
||||
typeDoc = "Type of the field"
|
||||
descriptionDoc = "Optional description of this field"
|
||||
|
||||
newCodec =
|
||||
-- the new codec which defers to LogicalModelType for all the
|
||||
-- complexities
|
||||
AC.CommentCodec
|
||||
("A field of a logical model")
|
||||
$ AC.object (backendPrefix @b <> "LogicalModelField")
|
||||
$ LogicalModelField
|
||||
<$> AC.requiredField "name" nameDoc
|
||||
AC..= lmfName
|
||||
<*> AC.requiredField "type" typeDoc
|
||||
AC..= lmfType
|
||||
<*> AC.optionalField "description" descriptionDoc
|
||||
AC..= lmfDescription
|
||||
where
|
||||
nameDoc = "Name of the field"
|
||||
typeDoc = "Type of the field"
|
||||
descriptionDoc = "Optional description of this field"
|
||||
|
||||
deriving via
|
||||
(AC.Autodocodec (LogicalModelField b))
|
||||
instance
|
||||
(Backend b) => (ToJSON (LogicalModelField b))
|
||||
|
||||
deriving via
|
||||
(AC.Autodocodec (LogicalModelField b))
|
||||
instance
|
||||
(Backend b) => FromJSON (LogicalModelField b)
|
||||
|
||||
deriving stock instance (Backend b) => Eq (LogicalModelField b)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user