private DocumentNode GenerateSqlGraphQLObjects()

in src/Core/Services/GraphQLSchemaCreator.cs [186:339]


        private DocumentNode GenerateSqlGraphQLObjects(RuntimeEntities entities, Dictionary<string, InputObjectTypeDefinitionNode> inputObjects)
        {
            // Dictionary to store:
            // 1. Object types for every entity exposed for MySql/PgSql/MsSql/DwSql in the config file.
            // 2. Object type for source->target linking object for M:N relationships to support insertion in the target table,
            // followed by an insertion in the linking table. The directional linking object contains all the fields from the target entity
            // (relationship/column) and non-relationship fields from the linking table.
            Dictionary<string, ObjectTypeDefinitionNode> objectTypes = new();

            Dictionary<string, EnumTypeDefinitionNode> enumTypes = new();

            // 1. Build up the object and input types for all the exposed entities in the config.
            foreach ((string entityName, Entity entity) in entities)
            {
                string dataSourceName = _runtimeConfigProvider.GetConfig().GetDataSourceNameFromEntityName(entityName);
                ISqlMetadataProvider sqlMetadataProvider = _metadataProviderFactory.GetMetadataProvider(dataSourceName);
                // Skip creating the GraphQL object for the current entity due to configuration
                // explicitly excluding the entity from the GraphQL endpoint.
                if (!entity.GraphQL.Enabled)
                {
                    continue;
                }

                if (sqlMetadataProvider.GetEntityNamesAndDbObjects().TryGetValue(entityName, out DatabaseObject? databaseObject))
                {
                    // Collection of role names allowed to access entity, to be added to the authorize directive
                    // of the objectTypeDefinitionNode. The authorize Directive is one of many directives created.
                    IEnumerable<string> rolesAllowedForEntity = _authorizationResolver.GetRolesForEntity(entityName);
                    Dictionary<string, IEnumerable<string>> rolesAllowedForFields = new();
                    SourceDefinition sourceDefinition = sqlMetadataProvider.GetSourceDefinition(entityName);
                    bool isStoredProcedure = entity.Source.Type is EntitySourceType.StoredProcedure;
                    foreach (string column in sourceDefinition.Columns.Keys)
                    {
                        EntityActionOperation operation = isStoredProcedure ? EntityActionOperation.Execute : EntityActionOperation.Read;
                        IEnumerable<string> roles = _authorizationResolver.GetRolesForField(entityName, field: column, operation: operation);
                        if (!rolesAllowedForFields.TryAdd(key: column, value: roles))
                        {
                            throw new DataApiBuilderException(
                                message: "Column already processed for building ObjectTypeDefinition authorization definition.",
                                statusCode: HttpStatusCode.InternalServerError,
                                subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization
                                );
                        }
                    }

                    // The roles allowed for Fields are the roles allowed to READ the fields, so any role that has a read definition for the field.
                    // Only add objectTypeDefinition for GraphQL if it has a role definition defined for access.
                    if (rolesAllowedForEntity.Any())
                    {
                        ObjectTypeDefinitionNode node = SchemaConverter.GenerateObjectTypeDefinitionForDatabaseObject(
                            entityName: entityName,
                            databaseObject: databaseObject,
                            configEntity: entity,
                            entities: entities,
                            rolesAllowedForEntity: rolesAllowedForEntity,
                            rolesAllowedForFields: rolesAllowedForFields);

                        if (databaseObject.SourceType is not EntitySourceType.StoredProcedure)
                        {
                            InputTypeBuilder.GenerateInputTypesForObjectType(node, inputObjects);

                            if (_isAggregationEnabled)
                            {
                                bool isAggregationEnumCreated = EnumTypeBuilder.GenerateAggregationNumericEnumForObjectType(node, enumTypes);
                                bool isGroupByColumnsEnumCreated = EnumTypeBuilder.GenerateScalarFieldsEnumForObjectType(node, enumTypes);
                                ObjectTypeDefinitionNode aggregationType;
                                ObjectTypeDefinitionNode groupByEntityNode;

                                // note: if aggregation enum is created, groupByColumnsEnum is also created as there would be scalar fields to groupby.
                                if (isAggregationEnumCreated)
                                {
                                    // Both aggregation and group by columns enum types are created for the entity. GroupBy should include fields and aggregation subfields.
                                    aggregationType = SchemaConverter.GenerateAggregationTypeForEntity(node.Name.Value, node);
                                    groupByEntityNode = SchemaConverter.GenerateGroupByTypeForEntity(node.Name.Value, node);
                                    IReadOnlyList<FieldDefinitionNode> groupByFields = groupByEntityNode.Fields;
                                    string aggregationsTypeName = SchemaConverter.GenerateObjectAggregationNodeName(node.Name.Value);
                                    FieldDefinitionNode aggregationNode = new(
                                        location: null,
                                        name: new NameNode(QueryBuilder.GROUP_BY_AGGREGATE_FIELD_NAME),
                                        description: new StringValueNode($"Aggregations for {entityName}"),
                                        arguments: new List<InputValueDefinitionNode>(),
                                        type: new NamedTypeNode(new NameNode(aggregationsTypeName)),
                                        directives: new List<DirectiveNode>()
                                    );
                                    List<FieldDefinitionNode> fieldDefinitionNodes = new(groupByFields) { aggregationNode };
                                    groupByEntityNode = groupByEntityNode.WithFields(fieldDefinitionNodes);
                                    objectTypes.Add(SchemaConverter.GenerateObjectAggregationNodeName(entityName), aggregationType);
                                    objectTypes.Add(SchemaConverter.GenerateGroupByTypeName(entityName), groupByEntityNode);
                                }
                                else if (isGroupByColumnsEnumCreated)
                                {
                                    // only groupBy enum is created for the entity. GroupBy should include fields but not aggregations.
                                    groupByEntityNode = SchemaConverter.GenerateGroupByTypeForEntity(entityName, node);
                                    objectTypes.Add(SchemaConverter.GenerateGroupByTypeName(entityName), groupByEntityNode);
                                }
                            }
                        }

                        objectTypes.Add(entityName, node);
                    }
                }
                else
                {
                    throw new DataApiBuilderException(message: $"Database Object definition for {entityName} has not been inferred.",
                        statusCode: HttpStatusCode.InternalServerError,
                        subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization);
                }
            }

            // ReferencingFieldDirective is added to eventually mark the referencing fields in the input object types as optional. When multiple create operations are disabled
            // the referencing fields should be required fields. Hence, ReferencingFieldDirective is added only when the multiple create operations are enabled.
            if (_isMultipleCreateOperationEnabled)
            {
                // For all the fields in the object which hold a foreign key reference to any referenced entity, add a foreign key directive.
                AddReferencingFieldDirective(entities, objectTypes);
            }

            // Pass two - Add the arguments to the many-to-* relationship fields
            foreach ((string entityName, ObjectTypeDefinitionNode node) in objectTypes)
            {
                objectTypes[entityName] = QueryBuilder.AddQueryArgumentsForRelationships(node, inputObjects);
            }

            // Create ObjectTypeDefinitionNode for linking entities. These object definitions are not exposed in the schema
            // but are used to generate the object definitions of directional linking entities for (source, target) and (target, source) entities.
            // However, ObjectTypeDefinitionNode for linking entities are need only for multiple create operation. So, creating these only when multiple create operations are
            // enabled.
            if (_isMultipleCreateOperationEnabled)
            {
                Dictionary<string, ObjectTypeDefinitionNode> linkingObjectTypes = GenerateObjectDefinitionsForLinkingEntities();
                GenerateSourceTargetLinkingObjectDefinitions(objectTypes, linkingObjectTypes);
            }

            // Return a list of all the object types to be exposed in the schema.
            Dictionary<string, FieldDefinitionNode> fields = new();

            // Add the DBOperationResult type to the schema
            NameNode nameNode = new(value: GraphQLUtils.DB_OPERATION_RESULT_TYPE);
            FieldDefinitionNode field = GetDbOperationResultField();

            fields.TryAdd(GraphQLUtils.DB_OPERATION_RESULT_FIELD_NAME, field);

            objectTypes.Add(GraphQLUtils.DB_OPERATION_RESULT_TYPE, new ObjectTypeDefinitionNode(
                location: null,
                name: nameNode,
                description: null,
                new List<DirectiveNode>(),
                new List<NamedTypeNode>(),
                fields.Values.ToImmutableList()));

            List<IDefinitionNode> nodes = new(objectTypes.Values);
            nodes.AddRange(enumTypes.Values);
            return new DocumentNode(nodes);
        }