Skip to content

fix: replace env defaults for stored procedures#3652

Open
nanookclaw wants to merge 1 commit into
Azure:mainfrom
nanookclaw:fix/stored-procedure-env-date-defaults
Open

fix: replace env defaults for stored procedures#3652
nanookclaw wants to merge 1 commit into
Azure:mainfrom
nanookclaw:fix/stored-procedure-env-date-defaults

Conversation

@nanookclaw
Copy link
Copy Markdown

Closes #2067.

Old-format stored procedure parameters are normalized in EntitySourceConverterFactory before the normal entity source deserialization path. That path copied string defaults with JsonElement.GetString(), so a config default like @env('year-end') was stored literally even when environment-variable replacement was enabled. Later SQL execute type coercion tried to convert the literal placeholder to DateTime and failed.

This updates the old-format parameter reader to deserialize string defaults through the existing string converter with replacement settings, leaving number, boolean, and null handling unchanged. That gives stored procedure config defaults the same environment-variable replacement behavior as other string config fields before SqlExecuteStructure coerces the value to the database parameter type.

Tests cover replacement-enabled and replacement-disabled config parsing for @env('year-end'), plus a focused execute-structure regression that verifies the substituted default becomes a DateTime parameter value.

Verification:

  • git diff --check
  • dotnet test src/Service.Tests/Azure.DataApiBuilder.Service.Tests.csproj --no-restore --filter "FullyQualifiedName~RuntimeConfigLoaderJsonDeserializerTests|FullyQualifiedName~SqlExecuteStructureUnitTests" could not run locally because dotnet is not installed in this container
  • dotnet build src/Config/Azure.DataApiBuilder.Config.csproj --no-restore could not run locally for the same reason

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR adds support for environment-variable replacement when deserializing stored procedure parameter defaults from runtime config, and adds tests to validate both config parsing and SQL execution parameter typing.

Changes:

  • Apply DeserializationVariableReplacementSettings when converting stored procedure parameter default values in EntitySourceConverter.
  • Add a deserialization unit test verifying stored procedure parameter default env-var replacement (on/off).
  • Add a SQL execution unit test verifying a config-provided default date is resolved as a DateTime parameter.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/Config/Converters/EntitySourceConverterFactory.cs Pass replacement settings into parameter default value deserialization so @env(...) can be replaced at parse time.
src/Service.Tests/UnitTests/RuntimeConfigLoaderJsonDeserializerTests.cs Adds a data-driven test covering env-var replacement behavior for stored procedure parameter defaults.
src/Service.Tests/UnitTests/SqlExecuteStructureUnitTests.cs Adds a unit test verifying config default date is materialized as a DateTime SQL parameter value.

Comment on lines +30 to +79
Environment.SetEnvironmentVariable(envVarName, "2024-06-30");
try
{
Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(
GetStoredProcedureConfigWithEnvDefault(),
out RuntimeConfig runtimeConfig,
replacementSettings: new DeserializationVariableReplacementSettings(
azureKeyVaultOptions: null,
doReplaceEnvVar: true,
doReplaceAkvVar: false)));

StoredProcedureDefinition storedProcedureDefinition = new();
storedProcedureDefinition.Parameters.Add(
parameterName,
new ParameterDefinition
{
SystemType = typeof(DateTime),
DbType = DbType.DateTime,
HasConfigDefault = true,
ConfigDefaultValue = runtimeConfig.Entities[entityName].Source.Parameters[0].Default
});

DatabaseStoredProcedure storedProcedure = new(schemaName: "dbo", tableName: "get_records_by_date")
{
SourceType = EntitySourceType.StoredProcedure,
StoredProcedureDefinition = storedProcedureDefinition
};

Mock<ISqlMetadataProvider> metadataProvider = new();
metadataProvider.SetupGet(x => x.EntityToDatabaseObject).Returns(new Dictionary<string, DatabaseObject>
{
{ entityName, storedProcedure }
});
metadataProvider.Setup(x => x.GetStoredProcedureDefinition(entityName)).Returns(storedProcedureDefinition);
metadataProvider.Setup(x => x.GetSourceDefinition(entityName)).Returns(storedProcedureDefinition);

SqlExecuteStructure executeStructure = new(
entityName,
metadataProvider.Object,
Mock.Of<IAuthorizationResolver>(),
null,
new Dictionary<string, object?>());

string dbConnectionParameterName = (string)executeStructure.ProcedureParameters[parameterName];
Assert.AreEqual(expectedDate, executeStructure.Parameters[dbConnectionParameterName].Value);
}
finally
{
Environment.SetEnvironmentVariable(envVarName, null);
}
Comment on lines +173 to +221
const string envVarName = "year-end";
Environment.SetEnvironmentVariable(envVarName, "2024-06-30");

try
{
string configJson = @"
{
""data-source"": {
""database-type"": ""mssql"",
""connection-string"": ""Server=tcp:127.0.0.1,1433;Persist Security Info=False;Trusted_Connection=True;TrustServerCertificate=True;MultipleActiveResultSets=False;Connection Timeout=5;""
},
""entities"": {
""GetRecordsByDate"": {
""source"": {
""object"": ""get_records_by_date"",
""type"": ""stored-procedure"",
""parameters"": {
""YearEndDate"": ""@env('year-end')""
}
},
""permissions"": [
{
""role"": ""anonymous"",
""actions"": [
{
""action"": ""execute""
}
]
}
]
}
}
}";

bool isParsingSuccessful = RuntimeConfigLoader.TryParseConfig(
configJson,
out RuntimeConfig runtimeConfig,
replacementSettings: new DeserializationVariableReplacementSettings(
azureKeyVaultOptions: null,
doReplaceEnvVar: replaceEnvVar,
doReplaceAkvVar: false));

Assert.IsTrue(isParsingSuccessful);
Assert.AreEqual(expectedDefaultValue, runtimeConfig.Entities["GetRecordsByDate"].Source.Parameters[0].Default);
}
finally
{
Environment.SetEnvironmentVariable(envVarName, null);
}
Comment on lines +127 to +132
private static string? DeserializeStringValue(JsonElement element, DeserializationVariableReplacementSettings? replacementSettings)
{
Utf8JsonReader reader = new(JsonSerializer.SerializeToUtf8Bytes(element.GetString()));
reader.Read();
return reader.DeserializeString(replacementSettings);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Possible issue with passing date values in env file to stored procedure

2 participants