Skip to content
55 changes: 55 additions & 0 deletions src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Globalization;
using System.Net;
using System.Text.Json;
using Azure.DataApiBuilder.Config.DatabasePrimitives;
using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Service.Exceptions;
using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars;
using Azure.DataApiBuilder.Service.GraphQLBuilder.Sql;
using HotChocolate.Language;
using HotChocolate.Types;
using HotChocolate.Types.NodaTime;
using NodaTime.Text;
using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLNaming;
using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedHotChocolateTypes;
using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLUtils;

namespace Azure.DataApiBuilder.Service.GraphQLBuilder
Expand Down Expand Up @@ -122,5 +129,53 @@ public static FieldDefinitionNode GetDefaultResultFieldForStoredProcedure()
type: new StringType().ToTypeNode(),
directives: new List<DirectiveNode>());
}

/// <summary>
/// Translates a JSON string or number value defined as a stored procedure's default value
/// within the runtime configuration to a GraphQL {Type}ValueNode which represents
/// the associated GraphQL type. The target value type is referenced from the passed in parameterDefinition which
/// holds database schema metadata.
/// </summary>
/// <param name="defaultValueFromConfig">String representation of default value defined in runtime config.</param>
/// <param name="parameterDefinition">Database schema metadata for stored procedure parameter which include value and value type.</param>
/// <returns>Tuple where first item is the string representation of a GraphQLType (e.g. "Byte", "Int", "Decimal")
/// and the second item is the GraphQL {type}ValueNode.</returns>
/// <exception cref="DataApiBuilderException">Raised when parameter casting fails due to unsupported type.</exception>
private static Tuple<string, IValueNode> ConvertValueToGraphQLType(string defaultValueFromConfig, ParameterDefinition parameterDefinition)
{
string paramValueType = SchemaConverter.GetGraphQLTypeFromSystemType(type: parameterDefinition.SystemType);

try
{
Tuple<string, IValueNode> valueNode = paramValueType switch
{
UUID_TYPE => new(UUID_TYPE, new UuidType().ParseValue(Guid.Parse(defaultValueFromConfig))),
BYTE_TYPE => new(BYTE_TYPE, new IntValueNode(byte.Parse(defaultValueFromConfig))),
SHORT_TYPE => new(SHORT_TYPE, new IntValueNode(short.Parse(defaultValueFromConfig))),
INT_TYPE => new(INT_TYPE, new IntValueNode(int.Parse(defaultValueFromConfig))),
LONG_TYPE => new(LONG_TYPE, new IntValueNode(long.Parse(defaultValueFromConfig))),
SINGLE_TYPE => new(SINGLE_TYPE, new SingleType().ParseValue(float.Parse(defaultValueFromConfig))),
FLOAT_TYPE => new(FLOAT_TYPE, new FloatValueNode(double.Parse(defaultValueFromConfig))),
DECIMAL_TYPE => new(DECIMAL_TYPE, new FloatValueNode(decimal.Parse(defaultValueFromConfig))),
STRING_TYPE => new(STRING_TYPE, new StringValueNode(defaultValueFromConfig)),
BOOLEAN_TYPE => new(BOOLEAN_TYPE, new BooleanValueNode(bool.Parse(defaultValueFromConfig))),
DATETIME_TYPE => new(DATETIME_TYPE, new DateTimeType().ParseResult(
DateTime.Parse(defaultValueFromConfig, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal))),
BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))),
LOCALTIME_TYPE => new(LOCALTIME_TYPE, new LocalTimeType().ParseResult(LocalTimePattern.ExtendedIso.Parse(defaultValueFromConfig).Value)),
_ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.")
};

return valueNode;
}
catch (Exception error)
{
throw new DataApiBuilderException(
message: $"The parameter value {defaultValueFromConfig} provided in configuration cannot be converted to the type {paramValueType}",
statusCode: HttpStatusCode.InternalServerError,
subStatusCode: DataApiBuilderException.SubStatusCodes.GraphQLMapping,
innerException: error);
}
}
}
}
62 changes: 3 additions & 59 deletions src/Service.GraphQLBuilder/GraphQLUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,14 @@
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net;
using Azure.DataApiBuilder.Config.DatabasePrimitives;
using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Service.Exceptions;
using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars;
using Azure.DataApiBuilder.Service.GraphQLBuilder.Directives;
using Azure.DataApiBuilder.Service.GraphQLBuilder.Queries;
using Azure.DataApiBuilder.Service.GraphQLBuilder.Sql;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using HotChocolate.Types;
using HotChocolate.Types.NodaTime;
using NodaTime.Text;
using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedHotChocolateTypes;

namespace Azure.DataApiBuilder.Service.GraphQLBuilder
Expand Down Expand Up @@ -46,9 +40,9 @@ public static bool IsModelType(ObjectType objectType)

public static bool IsBuiltInType(ITypeNode typeNode)
{
HashSet<string> inBuiltTypes = new()
HashSet<string> builtInTypes = new()
{
"ID",
"ID", // Required for CosmosDB
UUID_TYPE,
BYTE_TYPE,
SHORT_TYPE,
Expand All @@ -64,7 +58,7 @@ public static bool IsBuiltInType(ITypeNode typeNode)
LOCALTIME_TYPE
};
string name = typeNode.NamedType().Name.Value;
return inBuiltTypes.Contains(name);
return builtInTypes.Contains(name);
}

/// <summary>
Expand Down Expand Up @@ -225,56 +219,6 @@ public static ObjectType UnderlyingGraphQLEntityType(IType type)
return UnderlyingGraphQLEntityType(type.InnerType());
}

/// <summary>
/// Translates a JSON string or number value defined in the runtime configuration to a GraphQL {Type}ValueNode which represents
/// the associated GraphQL type. The target value type is referenced from the passed in parameterDefinition which
/// holds database schema metadata.
/// </summary>
/// <param name="defaultValueFromConfig">String representation of default value defined in runtime config.</param>
/// <param name="parameterDefinition">Database schema metadata for stored procedure parameter which include value and value type.</param>
/// <returns>Tuple where first item is the string representation of a GraphQLType (e.g. "Byte", "Int", "Decimal")
/// and the second item is the GraphQL {type}ValueNode </returns>
/// <exception cref="DataApiBuilderException">Raised when parameter casting fails due to unsupported type.</exception>
public static Tuple<string, IValueNode> ConvertValueToGraphQLType(string defaultValueFromConfig, ParameterDefinition parameterDefinition)
{
string paramValueType = SchemaConverter.GetGraphQLTypeFromSystemType(type: parameterDefinition.SystemType);

try
{
Tuple<string, IValueNode> valueNode = paramValueType switch
{
BYTE_TYPE => new(BYTE_TYPE, new IntValueNode(byte.Parse(defaultValueFromConfig))),
SHORT_TYPE => new(SHORT_TYPE, new IntValueNode(short.Parse(defaultValueFromConfig))),
INT_TYPE => new(INT_TYPE, new IntValueNode(int.Parse(defaultValueFromConfig))),
LONG_TYPE => new(LONG_TYPE, new IntValueNode(long.Parse(defaultValueFromConfig))),
STRING_TYPE => new(STRING_TYPE, new StringValueNode(defaultValueFromConfig)),
BOOLEAN_TYPE => new(BOOLEAN_TYPE, new BooleanValueNode(bool.Parse(defaultValueFromConfig))),
SINGLE_TYPE => new(SINGLE_TYPE, new SingleType().ParseValue(float.Parse(defaultValueFromConfig))),
FLOAT_TYPE => new(FLOAT_TYPE, new FloatValueNode(double.Parse(defaultValueFromConfig))),
DECIMAL_TYPE => new(DECIMAL_TYPE, new FloatValueNode(decimal.Parse(defaultValueFromConfig))),
DATETIME_TYPE => new(DATETIME_TYPE, new DateTimeType().ParseResult(
DateTime.Parse(defaultValueFromConfig, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal))),
BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))),
LOCALTIME_TYPE => new(LOCALTIME_TYPE, new LocalTimeType().ParseResult(LocalTimePattern.ExtendedIso.Parse(defaultValueFromConfig).Value)),
_ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.")
};

return valueNode;
}
catch (Exception error) when (
error is FormatException ||
error is OverflowException ||
error is ArgumentException ||
error is NotSupportedException)
{
throw new DataApiBuilderException(
message: $"The parameter value {defaultValueFromConfig} provided in configuration cannot be converted to the type {paramValueType}",
statusCode: HttpStatusCode.InternalServerError,
subStatusCode: DataApiBuilderException.SubStatusCodes.GraphQLMapping,
innerException: error);
}
}

/// <summary>
/// Generates the datasource name from the GraphQL context.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public class StoredProcedureBuilderTests
[DataRow(typeof(DateTimeOffset), DATETIME_TYPE, "11/19/2012 10:57:11 AM -08:00", false, DisplayName = "DateTimeOffset")]
[DataRow(typeof(TimeOnly), LOCALTIME_TYPE, "10:57:11.0000", false, DisplayName = "LocalTime")]
[DataRow(typeof(byte[]), BYTEARRAY_TYPE, "AgQGCAoMDhASFA==", false, DisplayName = "Byte[]")]
[DataRow(typeof(Guid), UUID_TYPE, "f58b7b58-62c9-4b97-ab60-75de70793f66", false, DisplayName = "GraphQL UUID/ SystemType GUID")]
[DataRow(typeof(string), STRING_TYPE, "f58b7b58-62c9-4b97-ab60-75de70793f66", false, DisplayName = "DB/SystemType String -> GUID value -> Resolve as GraphQL string")]
public void StoredProcedure_ParameterValueTypeResolution(
Type systemType,
string expectedGraphQLType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ public async Task TestTimeTypePrecisionCheck(string gqlValue, int count)
[DataRow(INT_TYPE, "0")]
[DataRow(INT_TYPE, "-9999")]
[DataRow(INT_TYPE, "null")]
[DataRow(UUID_TYPE, "3a1483a5-9ac2-4998-bcf3-78a28078c6ac")]
[DataRow(UUID_TYPE, "null")]
[DataRow(LONG_TYPE, "0")]
[DataRow(LONG_TYPE, "9000000000000000000")]
[DataRow(LONG_TYPE, "-9000000000000000000")]
Expand Down Expand Up @@ -419,6 +421,7 @@ public async Task InsertInvalidTimeIntoTimeTypeColumn(string type, string value)
[DataRow(TIME_TYPE, "\"23:59:59.9999999\"")]
[DataRow(TIME_TYPE, "null")]
[DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")]
[DataRow(UUID_TYPE, "3a1483a5-9ac2-4998-bcf3-78a28078c6ac")]
public async Task InsertIntoTypeColumnWithArgument(string type, object value)
{
if (!IsSupportedType(type))
Expand Down