From 8645608b46646f29d7a75258b39b923b1025feb5 Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Thu, 15 Feb 2024 11:26:45 -0800 Subject: [PATCH 1/4] When db type is unique identifier, enable GraphQL schema creation to resolve dabconfig provided default parameter value to resolve as a GraphQL/HotChocolate UUID and SystemType.Guid, which will allow startup to finish without error. Add unit test checking the affirmative and test to ensure db type string with dabconfig value looking like a GUID gets resolved as string --- src/Service.GraphQLBuilder/GraphQLUtils.cs | 1 + .../GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/Service.GraphQLBuilder/GraphQLUtils.cs b/src/Service.GraphQLBuilder/GraphQLUtils.cs index b98e13349f..f496de3744 100644 --- a/src/Service.GraphQLBuilder/GraphQLUtils.cs +++ b/src/Service.GraphQLBuilder/GraphQLUtils.cs @@ -247,6 +247,7 @@ public static Tuple ConvertValueToGraphQLType(string default 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)), + UUID_TYPE => new(UUID_TYPE, new UuidType().ParseValue(Guid.Parse(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))), diff --git a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs index a77a5f9dfa..50fdb327c2 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs @@ -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, From d4dadfdb07cd84c9ffeda3418e0577e6b39fcef7 Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Fri, 16 Feb 2024 09:06:10 -0800 Subject: [PATCH 2/4] updated integration tests to have more cases with UUID - GraphQL --- .../GraphQLSupportedTypesTestsBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index dc684d3eb2..0f121f21c1 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -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")] @@ -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)) From 99dded21991b02533b247652fc88c8304c541f8a Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Fri, 1 Mar 2024 12:55:42 -0800 Subject: [PATCH 3/4] move ConvertValueToGraphQLType() from GraphQLUtils.cs to GraphQLStoredProcedureBuilder to align with usage and purpose of function. Reduces size of MEGA UTIL class. Also adds comment for BuiltInTypes hashset entry 'ID' to inform that it enables CosmosDB functionality as only cosmos tests failed when that entry was commented out. Also reorganizes the ConvertValueToGraphQLType() order of types in switch statements to align with GraphQLUtils.BuiltInTypes hashset. --- .../GraphQLStoredProcedureBuilder.cs | 59 +++++++++++++++++ src/Service.GraphQLBuilder/GraphQLUtils.cs | 63 +------------------ 2 files changed, 62 insertions(+), 60 deletions(-) diff --git a/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs b/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs index 84837a90a2..4d5fa00aa5 100644 --- a/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs +++ b/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs @@ -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 @@ -122,5 +129,57 @@ public static FieldDefinitionNode GetDefaultResultFieldForStoredProcedure() type: new StringType().ToTypeNode(), directives: new List()); } + + /// + /// 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. + /// + /// String representation of default value defined in runtime config. + /// Database schema metadata for stored procedure parameter which include value and value type. + /// 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 + /// Raised when parameter casting fails due to unsupported type. + private static Tuple ConvertValueToGraphQLType(string defaultValueFromConfig, ParameterDefinition parameterDefinition) + { + string paramValueType = SchemaConverter.GetGraphQLTypeFromSystemType(type: parameterDefinition.SystemType); + + try + { + Tuple 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) 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); + } + } } } diff --git a/src/Service.GraphQLBuilder/GraphQLUtils.cs b/src/Service.GraphQLBuilder/GraphQLUtils.cs index fd32880032..62ae4c9e1b 100644 --- a/src/Service.GraphQLBuilder/GraphQLUtils.cs +++ b/src/Service.GraphQLBuilder/GraphQLUtils.cs @@ -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 @@ -46,9 +40,9 @@ public static bool IsModelType(ObjectType objectType) public static bool IsBuiltInType(ITypeNode typeNode) { - HashSet inBuiltTypes = new() + HashSet builtInTypes = new() { - "ID", + "ID", // Required for CosmosDB UUID_TYPE, BYTE_TYPE, SHORT_TYPE, @@ -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); } /// @@ -225,57 +219,6 @@ public static ObjectType UnderlyingGraphQLEntityType(IType type) return UnderlyingGraphQLEntityType(type.InnerType()); } - /// - /// 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. - /// - /// String representation of default value defined in runtime config. - /// Database schema metadata for stored procedure parameter which include value and value type. - /// 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 - /// Raised when parameter casting fails due to unsupported type. - public static Tuple ConvertValueToGraphQLType(string defaultValueFromConfig, ParameterDefinition parameterDefinition) - { - string paramValueType = SchemaConverter.GetGraphQLTypeFromSystemType(type: parameterDefinition.SystemType); - - try - { - Tuple 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)), - UUID_TYPE => new(UUID_TYPE, new UuidType().ParseValue(Guid.Parse(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); - } - } - /// /// Generates the datasource name from the GraphQL context. /// From 57f565375f4718d1830d60c44f5caecec9888c25 Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Fri, 1 Mar 2024 16:03:59 -0800 Subject: [PATCH 4/4] addressing feedback to removing specific exception type filtering so that the message on console when an exception is caught is readable. --- .../GraphQLStoredProcedureBuilder.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs b/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs index 4d5fa00aa5..4f94b5e3a4 100644 --- a/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs +++ b/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs @@ -139,7 +139,7 @@ public static FieldDefinitionNode GetDefaultResultFieldForStoredProcedure() /// String representation of default value defined in runtime config. /// Database schema metadata for stored procedure parameter which include value and value type. /// 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 + /// and the second item is the GraphQL {type}ValueNode. /// Raised when parameter casting fails due to unsupported type. private static Tuple ConvertValueToGraphQLType(string defaultValueFromConfig, ParameterDefinition parameterDefinition) { @@ -168,11 +168,7 @@ private static Tuple ConvertValueToGraphQLType(string defaul return valueNode; } - catch (Exception error) when ( - error is FormatException || - error is OverflowException || - error is ArgumentException || - error is NotSupportedException) + catch (Exception error) { throw new DataApiBuilderException( message: $"The parameter value {defaultValueFromConfig} provided in configuration cannot be converted to the type {paramValueType}",