Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Core/Services/GraphQLSchemaCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,8 @@ private DocumentNode GenerateSqlGraphQLObjects(RuntimeEntities entities, Diction
configEntity: entity,
entities: entities,
rolesAllowedForEntity: rolesAllowedForEntity,
rolesAllowedForFields: rolesAllowedForFields);
rolesAllowedForFields: rolesAllowedForFields,
databaseType: sqlMetadataProvider.GetDatabaseType());

if (databaseObject.SourceType is not EntitySourceType.StoredProcedure)
{
Expand Down Expand Up @@ -560,7 +561,8 @@ private Dictionary<string, ObjectTypeDefinitionNode> GenerateObjectDefinitionsFo
configEntity: linkingEntity,
entities: new(new Dictionary<string, Entity>()),
rolesAllowedForEntity: new List<string>(),
rolesAllowedForFields: new Dictionary<string, IEnumerable<string>>()
rolesAllowedForFields: new Dictionary<string, IEnumerable<string>>(),
databaseType: sqlMetadataProvider.GetDatabaseType()
);

linkingObjectTypes.Add(linkingEntityName, node);
Expand Down
26 changes: 19 additions & 7 deletions src/Service.GraphQLBuilder/Sql/SchemaConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public static ObjectTypeDefinitionNode GenerateObjectTypeDefinitionForDatabaseOb
Entity configEntity,
RuntimeEntities entities,
IEnumerable<string> rolesAllowedForEntity,
IDictionary<string, IEnumerable<string>> rolesAllowedForFields)
IDictionary<string, IEnumerable<string>> rolesAllowedForFields,
DatabaseType databaseType = DatabaseType.MSSQL)
{
ObjectTypeDefinitionNode objectDefinitionNode;
switch (databaseObject.SourceType)
Expand All @@ -69,7 +70,8 @@ public static ObjectTypeDefinitionNode GenerateObjectTypeDefinitionForDatabaseOb
configEntity: configEntity,
entities: entities,
rolesAllowedForEntity: rolesAllowedForEntity,
rolesAllowedForFields: rolesAllowedForFields);
rolesAllowedForFields: rolesAllowedForFields,
databaseType: databaseType);
break;
default:
throw new DataApiBuilderException(
Expand Down Expand Up @@ -170,7 +172,8 @@ private static ObjectTypeDefinitionNode CreateObjectTypeDefinitionForTableOrView
Entity configEntity,
RuntimeEntities entities,
IEnumerable<string> rolesAllowedForEntity,
IDictionary<string, IEnumerable<string>> rolesAllowedForFields)
IDictionary<string, IEnumerable<string>> rolesAllowedForFields,
DatabaseType databaseType = DatabaseType.MSSQL)
{
Dictionary<string, FieldDefinitionNode> fieldDefinitionNodes = new();
SourceDefinition sourceDefinition = databaseObject.SourceDefinition;
Expand Down Expand Up @@ -225,7 +228,8 @@ private static ObjectTypeDefinitionNode CreateObjectTypeDefinitionForTableOrView
databaseObject,
entities,
relationshipName,
relationship);
relationship,
databaseType);
fieldDefinitionNodes.Add(relationshipField.Name.Value, relationshipField);
}
}
Expand Down Expand Up @@ -474,13 +478,14 @@ private static FieldDefinitionNode GenerateFieldForRelationship(
DatabaseObject databaseObject,
RuntimeEntities entities,
string relationshipName,
EntityRelationship relationship)
EntityRelationship relationship,
DatabaseType databaseType = DatabaseType.MSSQL)
{
// Generate the field that represents the relationship to ObjectType, so you can navigate through it
// and walk the graph.
string targetEntityName = relationship.TargetEntity.Split('.').Last();
Entity referencedEntity = entities[targetEntityName];
bool isNullableRelationship = FindNullabilityOfRelationship(entityName, databaseObject, targetEntityName);
bool isNullableRelationship = FindNullabilityOfRelationship(entityName, databaseObject, targetEntityName, databaseType);

INullableTypeNode targetField = relationship.Cardinality switch
{
Expand Down Expand Up @@ -631,8 +636,15 @@ public static IValueNode CreateValueNodeFromDbObjectMetadata(object metadataValu
private static bool FindNullabilityOfRelationship(
string entityName,
DatabaseObject databaseObject,
string targetEntityName)
string targetEntityName,
DatabaseType databaseType = DatabaseType.MSSQL)
{
// DWSQL does not enforce foreign key constraints, so relationship fields are always nullable.
if (databaseType == DatabaseType.DWSQL)
{
return true;
}

bool isNullableRelationship = false;
SourceDefinition sourceDefinition = databaseObject.SourceDefinition;
if (// Retrieve all the relationship information for the source entity which is backed by this table definition
Expand Down
20 changes: 18 additions & 2 deletions src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,21 @@ public void ForeignKeyFieldHasCorrectNullability(bool isNullable)
Assert.AreEqual(expected: isNullable, actual: field.Type is INullableTypeNode);
}

// A NOT NULL foreign-key column yields a non-nullable relationship field for MSSQL, but a
// nullable one for DWSQL since DWSQL does not enforce foreign key constraints.
[DataRow(DatabaseType.DWSQL, Cardinality.One, true, DisplayName = "Many-to-one relationship field is nullable for DWSQL despite NOT NULL FK.")]
[DataRow(DatabaseType.DWSQL, Cardinality.Many, true, DisplayName = "Cardinality.Many relationship field is nullable for DWSQL despite NOT NULL FK metadata.")]
[DataRow(DatabaseType.MSSQL, Cardinality.One, false, DisplayName = "Many-to-one relationship field is non-nullable for MSSQL with NOT NULL FK.")]
[DataRow(DatabaseType.MSSQL, Cardinality.Many, false, DisplayName = "Cardinality.Many relationship field is non-nullable for MSSQL with NOT NULL FK metadata.")]
[TestMethod]
public void RelationshipFieldIsNullableForDwSqlDespiteNonNullableForeignKey(DatabaseType databaseType, Cardinality cardinality, bool expectedNullable)
{
ObjectTypeDefinitionNode od =
GenerateObjectWithRelationship(cardinality, isNullableRelationship: false, databaseType: databaseType);
FieldDefinitionNode field = od.Fields.First(f => f.Name.Value == FIELD_NAME_FOR_TARGET);
Assert.AreEqual(expectedNullable, field.Type is INullableTypeNode);
}

[TestMethod]
public void WhenForeignKeyDefinedButNoRelationship_GraphQLWontModelIt()
{
Expand Down Expand Up @@ -756,7 +771,7 @@ public static Entity GenerateEmptyEntity(string entityName)
);
}

private static ObjectTypeDefinitionNode GenerateObjectWithRelationship(Cardinality cardinality, bool isNullableRelationship = false)
private static ObjectTypeDefinitionNode GenerateObjectWithRelationship(Cardinality cardinality, bool isNullableRelationship = false, DatabaseType databaseType = DatabaseType.MSSQL)
{
SourceDefinition table = GenerateTableWithForeignKeyDefinition(isNullableRelationship);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This helper only sets up FK metadata going one direction, so it won't be correct for Cardinality.Many


Expand Down Expand Up @@ -786,7 +801,8 @@ private static ObjectTypeDefinitionNode GenerateObjectWithRelationship(Cardinali
dbObject,
configEntity, new(new Dictionary<string, Entity>() { { TARGET_ENTITY, relationshipEntity } }),
rolesAllowedForEntity: GetRolesAllowedForEntity(),
rolesAllowedForFields: GetFieldToRolesMap()
rolesAllowedForFields: GetFieldToRolesMap(),
databaseType: databaseType
);
}

Expand Down