in src/Core/Services/MetadataProviders/SqlMetadataProvider.cs [388:479]
protected virtual async Task FillSchemaForStoredProcedureAsync(
Entity procedureEntity,
string entityName,
string schemaName,
string storedProcedureSourceName,
StoredProcedureDefinition storedProcedureDefinition)
{
using ConnectionT conn = new();
conn.ConnectionString = ConnectionString;
DataTable procedureMetadata;
string[] procedureRestrictions = new string[NUMBER_OF_RESTRICTIONS];
try
{
await QueryExecutor.SetManagedIdentityAccessTokenIfAnyAsync(conn, _dataSourceName);
await conn.OpenAsync();
// To restrict the parameters for the current stored procedure, specify its name
procedureRestrictions[0] = conn.Database;
procedureRestrictions[1] = schemaName;
procedureRestrictions[2] = storedProcedureSourceName;
procedureMetadata = await conn.GetSchemaAsync(collectionName: "Procedures", restrictionValues: procedureRestrictions);
}
catch (Exception ex)
{
string message = $"Cannot obtain Schema for entity {entityName} " +
$"with underlying database object source: {schemaName}.{storedProcedureSourceName} " +
$"due to: {ex.Message}";
throw new DataApiBuilderException(
message: message,
innerException: ex,
statusCode: HttpStatusCode.ServiceUnavailable,
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization);
}
// Stored procedure does not exist in DB schema
if (procedureMetadata.Rows.Count == 0)
{
throw new DataApiBuilderException(
message: $"No stored procedure definition found for the given database object {storedProcedureSourceName}",
statusCode: HttpStatusCode.ServiceUnavailable,
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization);
}
// Each row in the procedureParams DataTable corresponds to a single parameter
DataTable parameterMetadata = await conn.GetSchemaAsync(collectionName: "ProcedureParameters", restrictionValues: procedureRestrictions);
// For each row/parameter, add an entry to StoredProcedureDefinition.Parameters dictionary
foreach (DataRow row in parameterMetadata.Rows)
{
// row["DATA_TYPE"] has value type string so a direct cast to System.Type is not supported.
// See https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-data-type-mappings
string sqlType = (string)row["DATA_TYPE"];
Type systemType = SqlToCLRType(sqlType);
ParameterDefinition paramDefinition = new()
{
SystemType = systemType,
DbType = TypeHelper.GetDbTypeFromSystemType(systemType)
};
// Add to parameters dictionary without the leading @ sign
storedProcedureDefinition.Parameters.TryAdd(((string)row["PARAMETER_NAME"])[1..], paramDefinition);
}
// Loop through parameters specified in config, throw error if not found in schema
// else set runtime config defined default values.
// Note: we defer type checking of parameters specified in config until request time
Dictionary<string, object>? configParameters = procedureEntity.Source.Parameters;
if (configParameters is not null)
{
foreach ((string configParamKey, object configParamValue) in configParameters)
{
if (!storedProcedureDefinition.Parameters.TryGetValue(configParamKey, out ParameterDefinition? parameterDefinition))
{
HandleOrRecordException(new DataApiBuilderException(
message: $"Could not find parameter \"{configParamKey}\" specified in config for procedure \"{schemaName}.{storedProcedureSourceName}\"",
statusCode: HttpStatusCode.ServiceUnavailable,
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization));
}
else
{
parameterDefinition.HasConfigDefault = true;
parameterDefinition.ConfigDefaultValue = configParamValue?.ToString();
}
}
}
// Generating exposed stored-procedure query/mutation name and adding to the dictionary mapping it to its entity name.
GraphQLStoredProcedureExposedNameToEntityNameMap.TryAdd(GenerateStoredProcedureGraphQLFieldName(entityName, procedureEntity), entityName);
}