Skip to content

Commit 8590aef

Browse files
[Cherry pick] Resolve uniqueidentifier database type from dab-config.json (#2075)
Full Title: Resolve `uniqueidentifier` database type from `dab-config.json` defined stored proc parameter default values. (#2042) ## Why make this change? - Closes #2027. DAB would not start up correctly when runtime config took the following form: ```json "entities": { "SpUuidParam": { "source": { "object": "sp_uuid_param", "type": "stored-procedure", "parameters": { "param1": "f58b7b58-62c9-4b97-ab60-75de70793f66" } }, "graphql": { "enabled": true, "operation": "Query", "type": { "singular": "SpUuidParam", "plural": "SpUuidParams" } }, "rest": { "enabled": true }, "permissions": [ { "role": "anonymous", "actions": [ "*" ] }, { "role": "authenticated", "actions": [ "*" ] } ] } ``` And stored proc in tsql ```tsql CREATE PROCEDURE [dbo].[sp_uuid_param] @param1 uniqueidentifier AS SELECT @param1 AS [ReturnPayload] ``` DAB was lacking the ability to handle the stored procedure having an input parameter with value type `uniqueidentifier`. When a default value was defined in the config, that value would fail conversion to the UUID type during GraphQL schema creation. The call stack flows through: ```csharp if (entity.Source.Parameters is not null && entity.Source.Parameters.TryGetValue(param, out object? value)) { Tuple<string, IValueNode> defaultGraphQLValue = ConvertValueToGraphQLType(value.ToString()!, parameterDefinition: spdef.Parameters[param]); defaultValueNode = defaultGraphQLValue.Item2; } ``` where `ConvertValueToGraphQLType(...)` would attempt to convert the config defined value to a GraphQL type based on the type inferred from the parameter's SystemType (System.Guid). The parameter object has the following properties when the parameter is passed to that function: - SystemType -> `System.Guid` - DbType -> `Guid` - ConfigDefaultValue -> `f58b7b58-62c9-4b97-ab60-75de70793f66` - HasConfigDefault -> `true` `ConvertValueToGraphQLType(...)` did not have the conversion needed to create a UUID type. ## What is this change? - In the function called to process config defined default values for stored procedure parameters, `ConvertValueToGraphQLType(...)` add the conversion: ```csharp UUID_TYPE => new(UUID_TYPE, new UuidType().ParseValue(Guid.Parse(defaultValueFromConfig))), ``` - Moved `ConvertValueToGraphQLType()` from `GraphQLUtils.cs` to `GraphQLStoredProcedureBuilder.cs` to align with usage and purpose of function. Reduces size of MEGA UTIL class. Also adds comment for `GraphQLUtils.BuiltInTypes` hashset entry 'ID' to inform that it enables CosmosDB functionality as only cosmos tests failed when that entry was commented out. - Reorganizes the ConvertValueToGraphQLType() order of types in switch statements to align with GraphQLUtils.BuiltInTypes hashset. That way it is easier to notice discrepancies in the two lists. ## How was this tested? - [x] Integration Tests ## Sample Request(s) - Use the db schema and entity config provided above. Startup will succeed without conversion errors when attempting to create the GraphQL schema. Co-authored-by: Aniruddh Munde <[email protected]>
1 parent 36c3791 commit 8590aef

File tree

4 files changed

+63
-59
lines changed

4 files changed

+63
-59
lines changed

src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Globalization;
5+
using System.Net;
46
using System.Text.Json;
57
using Azure.DataApiBuilder.Config.DatabasePrimitives;
68
using Azure.DataApiBuilder.Config.ObjectModel;
9+
using Azure.DataApiBuilder.Service.Exceptions;
10+
using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars;
711
using Azure.DataApiBuilder.Service.GraphQLBuilder.Sql;
812
using HotChocolate.Language;
913
using HotChocolate.Types;
14+
using HotChocolate.Types.NodaTime;
15+
using NodaTime.Text;
1016
using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLNaming;
17+
using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedHotChocolateTypes;
1118
using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLUtils;
1219

1320
namespace Azure.DataApiBuilder.Service.GraphQLBuilder
@@ -122,5 +129,53 @@ public static FieldDefinitionNode GetDefaultResultFieldForStoredProcedure()
122129
type: new StringType().ToTypeNode(),
123130
directives: new List<DirectiveNode>());
124131
}
132+
133+
/// <summary>
134+
/// Translates a JSON string or number value defined as a stored procedure's default value
135+
/// within the runtime configuration to a GraphQL {Type}ValueNode which represents
136+
/// the associated GraphQL type. The target value type is referenced from the passed in parameterDefinition which
137+
/// holds database schema metadata.
138+
/// </summary>
139+
/// <param name="defaultValueFromConfig">String representation of default value defined in runtime config.</param>
140+
/// <param name="parameterDefinition">Database schema metadata for stored procedure parameter which include value and value type.</param>
141+
/// <returns>Tuple where first item is the string representation of a GraphQLType (e.g. "Byte", "Int", "Decimal")
142+
/// and the second item is the GraphQL {type}ValueNode.</returns>
143+
/// <exception cref="DataApiBuilderException">Raised when parameter casting fails due to unsupported type.</exception>
144+
private static Tuple<string, IValueNode> ConvertValueToGraphQLType(string defaultValueFromConfig, ParameterDefinition parameterDefinition)
145+
{
146+
string paramValueType = SchemaConverter.GetGraphQLTypeFromSystemType(type: parameterDefinition.SystemType);
147+
148+
try
149+
{
150+
Tuple<string, IValueNode> valueNode = paramValueType switch
151+
{
152+
UUID_TYPE => new(UUID_TYPE, new UuidType().ParseValue(Guid.Parse(defaultValueFromConfig))),
153+
BYTE_TYPE => new(BYTE_TYPE, new IntValueNode(byte.Parse(defaultValueFromConfig))),
154+
SHORT_TYPE => new(SHORT_TYPE, new IntValueNode(short.Parse(defaultValueFromConfig))),
155+
INT_TYPE => new(INT_TYPE, new IntValueNode(int.Parse(defaultValueFromConfig))),
156+
LONG_TYPE => new(LONG_TYPE, new IntValueNode(long.Parse(defaultValueFromConfig))),
157+
SINGLE_TYPE => new(SINGLE_TYPE, new SingleType().ParseValue(float.Parse(defaultValueFromConfig))),
158+
FLOAT_TYPE => new(FLOAT_TYPE, new FloatValueNode(double.Parse(defaultValueFromConfig))),
159+
DECIMAL_TYPE => new(DECIMAL_TYPE, new FloatValueNode(decimal.Parse(defaultValueFromConfig))),
160+
STRING_TYPE => new(STRING_TYPE, new StringValueNode(defaultValueFromConfig)),
161+
BOOLEAN_TYPE => new(BOOLEAN_TYPE, new BooleanValueNode(bool.Parse(defaultValueFromConfig))),
162+
DATETIME_TYPE => new(DATETIME_TYPE, new DateTimeType().ParseResult(
163+
DateTime.Parse(defaultValueFromConfig, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal))),
164+
BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))),
165+
LOCALTIME_TYPE => new(LOCALTIME_TYPE, new LocalTimeType().ParseResult(LocalTimePattern.ExtendedIso.Parse(defaultValueFromConfig).Value)),
166+
_ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.")
167+
};
168+
169+
return valueNode;
170+
}
171+
catch (Exception error)
172+
{
173+
throw new DataApiBuilderException(
174+
message: $"The parameter value {defaultValueFromConfig} provided in configuration cannot be converted to the type {paramValueType}",
175+
statusCode: HttpStatusCode.InternalServerError,
176+
subStatusCode: DataApiBuilderException.SubStatusCodes.GraphQLMapping,
177+
innerException: error);
178+
}
179+
}
125180
}
126181
}

src/Service.GraphQLBuilder/GraphQLUtils.cs

Lines changed: 3 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,14 @@
22
// Licensed under the MIT License.
33

44
using System.Diagnostics.CodeAnalysis;
5-
using System.Globalization;
65
using System.Net;
7-
using Azure.DataApiBuilder.Config.DatabasePrimitives;
86
using Azure.DataApiBuilder.Config.ObjectModel;
97
using Azure.DataApiBuilder.Service.Exceptions;
10-
using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars;
118
using Azure.DataApiBuilder.Service.GraphQLBuilder.Directives;
129
using Azure.DataApiBuilder.Service.GraphQLBuilder.Queries;
13-
using Azure.DataApiBuilder.Service.GraphQLBuilder.Sql;
1410
using HotChocolate.Language;
1511
using HotChocolate.Resolvers;
1612
using HotChocolate.Types;
17-
using HotChocolate.Types.NodaTime;
18-
using NodaTime.Text;
1913
using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedHotChocolateTypes;
2014

2115
namespace Azure.DataApiBuilder.Service.GraphQLBuilder
@@ -45,9 +39,9 @@ public static bool IsModelType(ObjectType objectType)
4539

4640
public static bool IsBuiltInType(ITypeNode typeNode)
4741
{
48-
HashSet<string> inBuiltTypes = new()
42+
HashSet<string> builtInTypes = new()
4943
{
50-
"ID",
44+
"ID", // Required for CosmosDB
5145
UUID_TYPE,
5246
BYTE_TYPE,
5347
SHORT_TYPE,
@@ -63,7 +57,7 @@ public static bool IsBuiltInType(ITypeNode typeNode)
6357
LOCALTIME_TYPE
6458
};
6559
string name = typeNode.NamedType().Name.Value;
66-
return inBuiltTypes.Contains(name);
60+
return builtInTypes.Contains(name);
6761
}
6862

6963
/// <summary>
@@ -224,56 +218,6 @@ public static ObjectType UnderlyingGraphQLEntityType(IType type)
224218
return UnderlyingGraphQLEntityType(type.InnerType());
225219
}
226220

227-
/// <summary>
228-
/// Translates a JSON string or number value defined in the runtime configuration to a GraphQL {Type}ValueNode which represents
229-
/// the associated GraphQL type. The target value type is referenced from the passed in parameterDefinition which
230-
/// holds database schema metadata.
231-
/// </summary>
232-
/// <param name="defaultValueFromConfig">String representation of default value defined in runtime config.</param>
233-
/// <param name="parameterDefinition">Database schema metadata for stored procedure parameter which include value and value type.</param>
234-
/// <returns>Tuple where first item is the string representation of a GraphQLType (e.g. "Byte", "Int", "Decimal")
235-
/// and the second item is the GraphQL {type}ValueNode </returns>
236-
/// <exception cref="DataApiBuilderException">Raised when parameter casting fails due to unsupported type.</exception>
237-
public static Tuple<string, IValueNode> ConvertValueToGraphQLType(string defaultValueFromConfig, ParameterDefinition parameterDefinition)
238-
{
239-
string paramValueType = SchemaConverter.GetGraphQLTypeFromSystemType(type: parameterDefinition.SystemType);
240-
241-
try
242-
{
243-
Tuple<string, IValueNode> valueNode = paramValueType switch
244-
{
245-
BYTE_TYPE => new(BYTE_TYPE, new IntValueNode(byte.Parse(defaultValueFromConfig))),
246-
SHORT_TYPE => new(SHORT_TYPE, new IntValueNode(short.Parse(defaultValueFromConfig))),
247-
INT_TYPE => new(INT_TYPE, new IntValueNode(int.Parse(defaultValueFromConfig))),
248-
LONG_TYPE => new(LONG_TYPE, new IntValueNode(long.Parse(defaultValueFromConfig))),
249-
STRING_TYPE => new(STRING_TYPE, new StringValueNode(defaultValueFromConfig)),
250-
BOOLEAN_TYPE => new(BOOLEAN_TYPE, new BooleanValueNode(bool.Parse(defaultValueFromConfig))),
251-
SINGLE_TYPE => new(SINGLE_TYPE, new SingleType().ParseValue(float.Parse(defaultValueFromConfig))),
252-
FLOAT_TYPE => new(FLOAT_TYPE, new FloatValueNode(double.Parse(defaultValueFromConfig))),
253-
DECIMAL_TYPE => new(DECIMAL_TYPE, new FloatValueNode(decimal.Parse(defaultValueFromConfig))),
254-
DATETIME_TYPE => new(DATETIME_TYPE, new DateTimeType().ParseResult(
255-
DateTime.Parse(defaultValueFromConfig, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal))),
256-
BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))),
257-
LOCALTIME_TYPE => new(LOCALTIME_TYPE, new LocalTimeType().ParseResult(LocalTimePattern.ExtendedIso.Parse(defaultValueFromConfig).Value)),
258-
_ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.")
259-
};
260-
261-
return valueNode;
262-
}
263-
catch (Exception error) when (
264-
error is FormatException ||
265-
error is OverflowException ||
266-
error is ArgumentException ||
267-
error is NotSupportedException)
268-
{
269-
throw new DataApiBuilderException(
270-
message: $"The parameter value {defaultValueFromConfig} provided in configuration cannot be converted to the type {paramValueType}",
271-
statusCode: HttpStatusCode.InternalServerError,
272-
subStatusCode: DataApiBuilderException.SubStatusCodes.GraphQLMapping,
273-
innerException: error);
274-
}
275-
}
276-
277221
/// <summary>
278222
/// Generates the datasource name from the GraphQL context.
279223
/// </summary>

src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public class StoredProcedureBuilderTests
5656
[DataRow(typeof(DateTimeOffset), DATETIME_TYPE, "11/19/2012 10:57:11 AM -08:00", false, DisplayName = "DateTimeOffset")]
5757
[DataRow(typeof(TimeOnly), LOCALTIME_TYPE, "10:57:11.0000", false, DisplayName = "LocalTime")]
5858
[DataRow(typeof(byte[]), BYTEARRAY_TYPE, "AgQGCAoMDhASFA==", false, DisplayName = "Byte[]")]
59+
[DataRow(typeof(Guid), UUID_TYPE, "f58b7b58-62c9-4b97-ab60-75de70793f66", false, DisplayName = "GraphQL UUID/ SystemType GUID")]
60+
[DataRow(typeof(string), STRING_TYPE, "f58b7b58-62c9-4b97-ab60-75de70793f66", false, DisplayName = "DB/SystemType String -> GUID value -> Resolve as GraphQL string")]
5961
public void StoredProcedure_ParameterValueTypeResolution(
6062
Type systemType,
6163
string expectedGraphQLType,

src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ public async Task TestTimeTypePrecisionCheck(string gqlValue, int count)
314314
[DataRow(INT_TYPE, "0")]
315315
[DataRow(INT_TYPE, "-9999")]
316316
[DataRow(INT_TYPE, "null")]
317+
[DataRow(UUID_TYPE, "3a1483a5-9ac2-4998-bcf3-78a28078c6ac")]
318+
[DataRow(UUID_TYPE, "null")]
317319
[DataRow(LONG_TYPE, "0")]
318320
[DataRow(LONG_TYPE, "9000000000000000000")]
319321
[DataRow(LONG_TYPE, "-9000000000000000000")]
@@ -419,6 +421,7 @@ public async Task InsertInvalidTimeIntoTimeTypeColumn(string type, string value)
419421
[DataRow(TIME_TYPE, "\"23:59:59.9999999\"")]
420422
[DataRow(TIME_TYPE, "null")]
421423
[DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")]
424+
[DataRow(UUID_TYPE, "3a1483a5-9ac2-4998-bcf3-78a28078c6ac")]
422425
public async Task InsertIntoTypeColumnWithArgument(string type, object value)
423426
{
424427
if (!IsSupportedType(type))

0 commit comments

Comments
 (0)