From 800de2bbbeed0d7fef7d61ccb6de162ac599a74c Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Fri, 31 Mar 2023 16:24:08 -0700 Subject: [PATCH 01/50] Add support for timespan --- .../GraphQLTypes/DefaultValueType.cs | 1 + .../GraphQLTypes/SupportedTypes.cs | 3 + src/Service.GraphQLBuilder/GraphQLUtils.cs | 3 +- .../Queries/StandardQueryInputs.cs | 20 ++++- .../Sql/SchemaConverter.cs | 2 + src/Service.Tests/DatabaseSchema-MsSql.sql | 32 +++++-- .../Sql/SchemaConverterTests.cs | 1 + src/Service/Parsers/EdmModelBuilder.cs | 87 ++++++++----------- 8 files changed, 92 insertions(+), 57 deletions(-) diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs b/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs index 2ce9d6b95e..1a97f76a81 100644 --- a/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs +++ b/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs @@ -24,6 +24,7 @@ protected override void Configure(IInputObjectTypeDescriptor descriptor) descriptor.Field(DECIMAL_TYPE).Type(); descriptor.Field(DATETIME_TYPE).Type(); descriptor.Field(BYTEARRAY_TYPE).Type(); + descriptor.Field(TIMESPAN_TYPE).Type(); } } } diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs b/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs index 52c1b25ad4..54b87936b5 100644 --- a/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs +++ b/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs @@ -18,8 +18,11 @@ public static class SupportedTypes public const string STRING_TYPE = "String"; public const string BOOLEAN_TYPE = "Boolean"; public const string DATETIME_TYPE = "DateTime"; + // The DATETIME_NONUTC_TYPE constant is only used in testing + // since PostgreSQL doesn't support datetime values with a non-UTC time zone. public const string DATETIME_NONUTC_TYPE = "DateTimeNonUTC"; public const string BYTEARRAY_TYPE = "ByteArray"; + public const string TIMESPAN_TYPE = "TimeSpan"; public const string GUID_TYPE = "Guid"; } } diff --git a/src/Service.GraphQLBuilder/GraphQLUtils.cs b/src/Service.GraphQLBuilder/GraphQLUtils.cs index 29b361d688..836c63004d 100644 --- a/src/Service.GraphQLBuilder/GraphQLUtils.cs +++ b/src/Service.GraphQLBuilder/GraphQLUtils.cs @@ -48,7 +48,8 @@ public static bool IsBuiltInType(ITypeNode typeNode) STRING_TYPE, BOOLEAN_TYPE, DATETIME_TYPE, - BYTEARRAY_TYPE + BYTEARRAY_TYPE, + TIMESPAN_TYPE }; string name = typeNode.NamedType().Name.Value; return inBuiltTypes.Contains(name); diff --git a/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs b/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs index 69ead89827..190c0d2c52 100644 --- a/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs +++ b/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs @@ -190,6 +190,23 @@ public static InputObjectTypeDefinitionNode DateTimeInputType() => } ); + public static InputObjectTypeDefinitionNode TimeSpanInputType() => + new( + location: null, + new NameNode("TimeSpanFilterInput"), + new StringValueNode("Input type for adding TimeSpan filters"), + new List(), + new List { + new InputValueDefinitionNode(null, new NameNode("eq"), new StringValueNode("Equals"), new TimeSpanType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("gt"), new StringValueNode("Greater Than"), new TimeSpanType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("gte"), new StringValueNode("Greater Than or Equal To"), new TimeSpanType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("lt"), new StringValueNode("Less Than"), new TimeSpanType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("lte"), new StringValueNode("Less Than or Equal To"), new TimeSpanType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("neq"), new StringValueNode("Not Equals"), new TimeSpanType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("isNull"), new StringValueNode("is null test"), new BooleanType().ToTypeNode(), null, new List()) + } + ); + public static InputObjectTypeDefinitionNode ByteArrayInputType() => new( location: null, @@ -214,7 +231,8 @@ public static InputObjectTypeDefinitionNode ByteArrayInputType() => { BOOLEAN_TYPE, BooleanInputType() }, { STRING_TYPE, StringInputType() }, { DATETIME_TYPE, DateTimeInputType() }, - { BYTEARRAY_TYPE, ByteArrayInputType() } + { BYTEARRAY_TYPE, ByteArrayInputType() }, + { TIMESPAN_TYPE, TimeSpanInputType() }, }; /// diff --git a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs index 521e59616a..f5dc37e10f 100644 --- a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs +++ b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs @@ -82,6 +82,7 @@ public static ObjectTypeDefinitionNode FromDatabaseObject( DateTime value => new ObjectValueNode(new ObjectFieldNode(DATETIME_TYPE, new DateTimeType().ParseResult(value))), DateTimeOffset value => new ObjectValueNode(new ObjectFieldNode(DATETIME_TYPE, new DateTimeType().ParseValue(value))), byte[] value => new ObjectValueNode(new ObjectFieldNode(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(value))), + TimeSpan value => new ObjectValueNode(new ObjectFieldNode(TIMESPAN_TYPE, new TimeSpanType().ParseValue(value))), _ => throw new DataApiBuilderException( message: $"The type {column.DefaultValue.GetType()} is not supported as a GraphQL default value", statusCode: HttpStatusCode.InternalServerError, @@ -244,6 +245,7 @@ public static string GetGraphQLTypeForColumnType(Type type) "Boolean" => BOOLEAN_TYPE, "DateTime" => DATETIME_TYPE, "Byte[]" => BYTEARRAY_TYPE, + "TimeSpan" => TIMESPAN_TYPE, _ => throw new DataApiBuilderException( message: $"Column type {type} not handled by case. Please add a case resolving {type} to the appropriate GraphQL type", statusCode: HttpStatusCode.InternalServerError, diff --git a/src/Service.Tests/DatabaseSchema-MsSql.sql b/src/Service.Tests/DatabaseSchema-MsSql.sql index f8dfb7a171..4488fb080b 100644 --- a/src/Service.Tests/DatabaseSchema-MsSql.sql +++ b/src/Service.Tests/DatabaseSchema-MsSql.sql @@ -143,7 +143,12 @@ CREATE TABLE type_table( float_types float, decimal_types decimal(38, 19), boolean_types bit, + date_types date, datetime_types datetime, + datetime2_types datetime2, + datetimeoffset_types datetimeoffset, + smalldatetime_types smalldatetime, + time_types time, bytearray_types varbinary(max), guid_types uniqueidentifier DEFAULT newid() ); @@ -359,12 +364,27 @@ INSERT INTO reviews(id, book_id, content) VALUES (567, 1, 'Indeed a great book') SET IDENTITY_INSERT reviews OFF SET IDENTITY_INSERT type_table ON -INSERT INTO type_table(id, byte_types, short_types, int_types, long_types, string_types, single_types, float_types, decimal_types, boolean_types, datetime_types, bytearray_types) VALUES - (1, 1, 1, 1, 1, '', 0.33, 0.33, 0.333333, 1, '1999-01-08 10:23:54', 0xABCDEF0123), - (2, 0, -1, -1, -1, 'lksa;jdflasdf;alsdflksdfkldj', -9.2, -9.2, -9.292929, 0, '1999-01-08 10:23:00', 0x98AB7511AABB1234), - (3, 0, -32768, -2147483648, -9223372036854775808, 'null', -3.4E38, -1.7E308, 2.929292E-19, 1, '1753-01-01 00:00:00.000', 0x00000000), - (4, 255, 32767, 2147483647, 9223372036854775807, 'null', 3.4E38, 1.7E308, 2.929292E-14, 1, '9999-12-31 23:59:59', 0xFFFFFFFF), - (5, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO type_table(id, +byte_types, short_types, int_types, long_types, +string_types, +single_types, float_types, decimal_types, +boolean_types, +date_types, datetime_types, datetime2_types, datetimeoffset_types, smalldatetime_types, time_types, +bytearray_types) +VALUES + (1, 1, 1, 1, 1, '', 0.33, 0.33, 0.333333, 1, + '1999-01-08', '1999-01-08 10:23:54', '1999-01-08 10:23:54.9999999', '1999-01-08 10:23:54.9999999-14:00', '1999-01-08 10:23:54', '10:23:54.9999999', + 0xABCDEF0123), + (2, 0, -1, -1, -1, 'lksa;jdflasdf;alsdflksdfkldj', -9.2, -9.2, -9.292929, 0, + '1999-01-08', '1999-01-08 10:23:00', '1999-01-08 10:23:00.9999999', '1999-01-08 10:23:00.9999999+13:00', '1999-01-08 10:23:00', '10:23:00.9999999', + 0x98AB7511AABB1234), + (3, 0, -32768, -2147483648, -9223372036854775808, 'null', -3.4E38, -1.7E308, 2.929292E-19, 1, + '0001-01-01', '1753-01-01 00:00:00.000', '0001-01-01 00:00:00.0000000', '0001-01-01 00:00:00.0000000+0:00', '1900-01-01 00:00:00', '00:00:00.0000000', + 0x00000000), + (4, 255, 32767, 2147483647, 9223372036854775807, 'null', 3.4E38, 1.7E308, 2.929292E-14, 1, + '9999-12-31', '9999-12-31 23:59:59', '9999-12-31 23:59:59.9999999', '9999-12-31 23:59:59.9999999+14:00', '2079-06-06', '23:59:59.9999999', + 0xFFFFFFFF), + (5, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); SET IDENTITY_INSERT type_table OFF SET IDENTITY_INSERT sales ON diff --git a/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs index 20bf8d158a..b94f6e1116 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs @@ -227,6 +227,7 @@ public void MultipleColumnsAllMapped() [DataRow(typeof(bool), BOOLEAN_TYPE)] [DataRow(typeof(DateTime), DATETIME_TYPE)] [DataRow(typeof(byte[]), BYTEARRAY_TYPE)] + [DataRow(typeof(TimeSpan), TIMESPAN_TYPE)] public void SystemTypeMapsToCorrectGraphQLType(Type systemType, string graphQLType) { SourceDefinition table = new(); diff --git a/src/Service/Parsers/EdmModelBuilder.cs b/src/Service/Parsers/EdmModelBuilder.cs index 1279c4817a..28ab5669f3 100644 --- a/src/Service/Parsers/EdmModelBuilder.cs +++ b/src/Service/Parsers/EdmModelBuilder.cs @@ -69,56 +69,9 @@ SourceDefinition sourceDefinition // each column represents a property of the current entity we are adding foreach (string column in sourceDefinition.Columns.Keys) { - // need to convert our column system type to an Edm type Type columnSystemType = sourceDefinition.Columns[column].SystemType; - EdmPrimitiveTypeKind type = EdmPrimitiveTypeKind.None; - if (columnSystemType.IsArray) - { - columnSystemType = columnSystemType.GetElementType()!; - } - - switch (columnSystemType.Name) - { - case "String": - type = EdmPrimitiveTypeKind.String; - break; - case "Guid": - type = EdmPrimitiveTypeKind.Guid; - break; - case "Byte": - type = EdmPrimitiveTypeKind.Byte; - break; - case "Int16": - type = EdmPrimitiveTypeKind.Int16; - break; - case "Int32": - type = EdmPrimitiveTypeKind.Int32; - break; - case "Int64": - type = EdmPrimitiveTypeKind.Int64; - break; - case "Single": - type = EdmPrimitiveTypeKind.Single; - break; - case "Double": - type = EdmPrimitiveTypeKind.Double; - break; - case "Decimal": - type = EdmPrimitiveTypeKind.Decimal; - break; - case "Boolean": - type = EdmPrimitiveTypeKind.Boolean; - break; - case "DateTime": - type = EdmPrimitiveTypeKind.DateTimeOffset; - break; - case "Date": - type = EdmPrimitiveTypeKind.Date; - break; - default: - throw new ArgumentException($"Column type" + - $" {columnSystemType.Name} not yet supported."); - } + // need to convert our column system type to an Edm type + EdmPrimitiveTypeKind type = GetEdmPrimitiveTypeFromSystemType(columnSystemType); // here we must use the correct aliasing for the column name // which is on a per entity basis. @@ -149,6 +102,42 @@ SourceDefinition sourceDefinition return this; } + /// + /// Given the system type, returns the corresponding primitive type kind. + /// + /// Type of the column. + /// EdmPrimitiveTypeKind + /// Throws when the column + private static EdmPrimitiveTypeKind GetEdmPrimitiveTypeFromSystemType(Type columnSystemType) + { + if (columnSystemType.IsArray) + { + columnSystemType = columnSystemType.GetElementType()!; + } + + EdmPrimitiveTypeKind type = columnSystemType.Name switch + { + "String" => EdmPrimitiveTypeKind.String, + "Guid" => EdmPrimitiveTypeKind.Guid, + "Byte" => EdmPrimitiveTypeKind.Byte, + "Int16" => EdmPrimitiveTypeKind.Int16, + "Int32" => EdmPrimitiveTypeKind.Int32, + "Int64" => EdmPrimitiveTypeKind.Int64, + "Single" => EdmPrimitiveTypeKind.Single, + "Double" => EdmPrimitiveTypeKind.Double, + "Decimal" => EdmPrimitiveTypeKind.Decimal, + "Boolean" => EdmPrimitiveTypeKind.Boolean, + "DateTime" => EdmPrimitiveTypeKind.DateTimeOffset, + "DateTimeOffset" => EdmPrimitiveTypeKind.DateTimeOffset, + "Date" => EdmPrimitiveTypeKind.Date, + "TimeSpan" => EdmPrimitiveTypeKind.TimeOfDay, + _ => throw new ArgumentException($"Column type" + + $" {columnSystemType.Name} not yet supported.") + }; + + return type; + } + /// /// Add the entity sets contained within the schema to container. /// From 0f12bd1cc62208ba0e2897352a44d52eb51663c0 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Fri, 5 May 2023 09:57:36 -0700 Subject: [PATCH 02/50] remove unused using --- src/Service.GraphQLBuilder/Sql/SchemaConverter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs index 055d659822..47fccc9033 100644 --- a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs +++ b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Net; From 736724440aed672ce77febfb6a941b43e0a57b75 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Fri, 5 May 2023 13:51:01 -0700 Subject: [PATCH 03/50] Add a case for TimeSpanType in ResolverMiddleware --- src/Service/Services/DbTypeHelper.cs | 2 ++ src/Service/Services/ResolverMiddleware.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Service/Services/DbTypeHelper.cs b/src/Service/Services/DbTypeHelper.cs index 57a63b6fd1..e1f7a868a5 100644 --- a/src/Service/Services/DbTypeHelper.cs +++ b/src/Service/Services/DbTypeHelper.cs @@ -30,6 +30,7 @@ public static class DbTypeHelper [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, [typeof(byte[])] = DbType.Binary, + [typeof(TimeSpan)] = DbType.Time, [typeof(byte?)] = DbType.Byte, [typeof(sbyte?)] = DbType.SByte, [typeof(short?)] = DbType.Int16, @@ -44,6 +45,7 @@ public static class DbTypeHelper [typeof(bool?)] = DbType.Boolean, [typeof(char?)] = DbType.StringFixedLength, [typeof(Guid?)] = DbType.Guid, + [typeof(TimeSpan?)] = DbType.Time, [typeof(object)] = DbType.Object }; diff --git a/src/Service/Services/ResolverMiddleware.cs b/src/Service/Services/ResolverMiddleware.cs index 39e91c8352..afdd82e822 100644 --- a/src/Service/Services/ResolverMiddleware.cs +++ b/src/Service/Services/ResolverMiddleware.cs @@ -202,6 +202,7 @@ private static object PreParseLeaf(IMiddlewareContext context, string leafJson) SingleType => Single.Parse(leafJson), DateTimeType => DateTimeOffset.Parse(leafJson), ByteArrayType => Convert.FromBase64String(leafJson), + TimeSpanType => TimeSpan.Parse(leafJson), _ => leafJson }; } From 221baadc092cf83048f63961d724c7c89fdbb923 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Fri, 5 May 2023 13:52:30 -0700 Subject: [PATCH 04/50] enclose name in [] --- src/Service.Tests/DatabaseSchema-MsSql.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service.Tests/DatabaseSchema-MsSql.sql b/src/Service.Tests/DatabaseSchema-MsSql.sql index 6bc0044714..4768723a12 100644 --- a/src/Service.Tests/DatabaseSchema-MsSql.sql +++ b/src/Service.Tests/DatabaseSchema-MsSql.sql @@ -66,7 +66,7 @@ CREATE TABLE books( CREATE TABLE players( id int IDENTITY(5001, 1) PRIMARY KEY, - name varchar(max) NOT NULL, + [name] varchar(max) NOT NULL, current_club_id int NOT NULL, new_club_id int NOT NULL ); @@ -378,7 +378,7 @@ VALUES (1, 'Awesome book', 1234), SET IDENTITY_INSERT books OFF SET IDENTITY_INSERT players ON -INSERT INTO players(id, name, current_club_id, new_club_id) +INSERT INTO players(id, [name], current_club_id, new_club_id) VALUES (1, 'Cristiano Ronaldo', 1113, 1111), (2, 'Leonel Messi', 1112, 1113); SET IDENTITY_INSERT players OFF From d210db2f922e2401d4368efdddbef278f38e4980 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Fri, 5 May 2023 18:26:11 -0700 Subject: [PATCH 05/50] Support timespan as stored procedure parameter --- src/Service.GraphQLBuilder/GraphQLUtils.cs | 1 + .../GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Service.GraphQLBuilder/GraphQLUtils.cs b/src/Service.GraphQLBuilder/GraphQLUtils.cs index d3271edf51..ffe4be1a87 100644 --- a/src/Service.GraphQLBuilder/GraphQLUtils.cs +++ b/src/Service.GraphQLBuilder/GraphQLUtils.cs @@ -240,6 +240,7 @@ public static Tuple ConvertValueToGraphQLType(string default 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))), + TIMESPAN_TYPE => new(TIMESPAN_TYPE, new TimeSpanType().ParseValue(defaultValueFromConfig)), BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))), _ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.") }; diff --git a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs index 81f3bd32f7..71dd280d41 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs @@ -53,6 +53,7 @@ public class StoredProcedureBuilderTests [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12:00:00 AM", false, DisplayName = "DateTime")] [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12000 AM", true, DisplayName = "DateTime")] [DataRow(typeof(DateTimeOffset), DATETIME_TYPE, "11/19/2012 10:57:11 AM -08:00", false, DisplayName = "DateTimeOffset")] + [DataRow(typeof(TimeSpan), TIMESPAN_TYPE, "10:57:11 AM -08:00", false, DisplayName = "TimeSpan")] [DataRow(typeof(byte[]), BYTEARRAY_TYPE, "AgQGCAoMDhASFA==", false, DisplayName = "Byte[]")] public void StoredProcedure_ParameterValueTypeResolution( Type systemType, From 296511f0e8ce597f71e173c10bfced2dbe6d3527 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Fri, 5 May 2023 20:53:50 -0700 Subject: [PATCH 06/50] Parse with invariant culture and assume UTC --- src/Service.GraphQLBuilder/GraphQLUtils.cs | 6 ++++-- src/Service/Services/ResolverMiddleware.cs | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Service.GraphQLBuilder/GraphQLUtils.cs b/src/Service.GraphQLBuilder/GraphQLUtils.cs index ffe4be1a87..53928ea3c4 100644 --- a/src/Service.GraphQLBuilder/GraphQLUtils.cs +++ b/src/Service.GraphQLBuilder/GraphQLUtils.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Net; using Azure.DataApiBuilder.Config; using Azure.DataApiBuilder.Service.Exceptions; @@ -239,8 +240,9 @@ public static Tuple ConvertValueToGraphQLType(string default 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))), - TIMESPAN_TYPE => new(TIMESPAN_TYPE, new TimeSpanType().ParseValue(defaultValueFromConfig)), + DATETIME_TYPE => new(DATETIME_TYPE, new DateTimeType().ParseResult( + DateTime.Parse(defaultValueFromConfig, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal))), + TIMESPAN_TYPE => new(TIMESPAN_TYPE, new TimeSpanType().ParseResult(defaultValueFromConfig)), BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))), _ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.") }; diff --git a/src/Service/Services/ResolverMiddleware.cs b/src/Service/Services/ResolverMiddleware.cs index afdd82e822..f63eadd3e0 100644 --- a/src/Service/Services/ResolverMiddleware.cs +++ b/src/Service/Services/ResolverMiddleware.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text.Json; using System.Threading.Tasks; using Azure.DataApiBuilder.Service.Authorization; @@ -200,9 +201,9 @@ private static object PreParseLeaf(IMiddlewareContext context, string leafJson) { ByteType => byte.Parse(leafJson), SingleType => Single.Parse(leafJson), - DateTimeType => DateTimeOffset.Parse(leafJson), + DateTimeType => DateTimeOffset.Parse(leafJson, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), ByteArrayType => Convert.FromBase64String(leafJson), - TimeSpanType => TimeSpan.Parse(leafJson), + TimeSpanType => TimeSpan.Parse(leafJson, DateTimeFormatInfo.InvariantInfo), _ => leafJson }; } From 8c23ffe500062ceea0ecbc5b186c9934b5fc562b Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Fri, 5 May 2023 20:54:36 -0700 Subject: [PATCH 07/50] StoredProc default paramvalue --- .../GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs index 71dd280d41..13642ffa72 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs @@ -53,7 +53,7 @@ public class StoredProcedureBuilderTests [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12:00:00 AM", false, DisplayName = "DateTime")] [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12000 AM", true, DisplayName = "DateTime")] [DataRow(typeof(DateTimeOffset), DATETIME_TYPE, "11/19/2012 10:57:11 AM -08:00", false, DisplayName = "DateTimeOffset")] - [DataRow(typeof(TimeSpan), TIMESPAN_TYPE, "10:57:11 AM -08:00", false, DisplayName = "TimeSpan")] + [DataRow(typeof(TimeSpan), TIMESPAN_TYPE, "10-57-11", false, DisplayName = "TimeSpan")] [DataRow(typeof(byte[]), BYTEARRAY_TYPE, "AgQGCAoMDhASFA==", false, DisplayName = "Byte[]")] public void StoredProcedure_ParameterValueTypeResolution( Type systemType, From b0001688e2fe38ddc503b4ed0ea674a0cc0c5821 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Sat, 6 May 2023 18:06:32 -0700 Subject: [PATCH 08/50] TimeSpan Iso8601 format is PT23H59M59.999999S --- .../GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs | 2 +- .../Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs index 13642ffa72..9c936652ac 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs @@ -53,7 +53,7 @@ public class StoredProcedureBuilderTests [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12:00:00 AM", false, DisplayName = "DateTime")] [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12000 AM", true, DisplayName = "DateTime")] [DataRow(typeof(DateTimeOffset), DATETIME_TYPE, "11/19/2012 10:57:11 AM -08:00", false, DisplayName = "DateTimeOffset")] - [DataRow(typeof(TimeSpan), TIMESPAN_TYPE, "10-57-11", false, DisplayName = "TimeSpan")] + [DataRow(typeof(TimeSpan), TIMESPAN_TYPE, "PT23H59M59.9999999S", false, DisplayName = "TimeSpan")] [DataRow(typeof(byte[]), BYTEARRAY_TYPE, "AgQGCAoMDhASFA==", false, DisplayName = "Byte[]")] public void StoredProcedure_ParameterValueTypeResolution( Type systemType, diff --git a/src/Service/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs index 39fe1d9066..8a2cc1d9d5 100644 --- a/src/Service/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs +++ b/src/Service/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -364,9 +365,10 @@ protected object ParseParamAsSystemType(string param, Type systemType) "Double" => double.Parse(param), "Decimal" => decimal.Parse(param), "Boolean" => bool.Parse(param), - "DateTime" => DateTimeOffset.Parse(param), + "DateTime" => DateTimeOffset.Parse(param, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), "DateTimeOffset" => DateTimeOffset.Parse(param), "Date" => DateOnly.Parse(param), + "TimeSpan" => TimeSpan.Parse(param, DateTimeFormatInfo.InvariantInfo), "Guid" => Guid.Parse(param), _ => throw new NotSupportedException($"{systemType.Name} is not supported") }; From 1c35eb5105bd0f30aea36a37f37515ad7120c65a Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 8 May 2023 09:52:17 +0530 Subject: [PATCH 09/50] fixing tests --- src/Service.GraphQLBuilder/GraphQLUtils.cs | 2 +- .../GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service.GraphQLBuilder/GraphQLUtils.cs b/src/Service.GraphQLBuilder/GraphQLUtils.cs index 53928ea3c4..5dcbfb052b 100644 --- a/src/Service.GraphQLBuilder/GraphQLUtils.cs +++ b/src/Service.GraphQLBuilder/GraphQLUtils.cs @@ -242,7 +242,7 @@ public static Tuple ConvertValueToGraphQLType(string default 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))), - TIMESPAN_TYPE => new(TIMESPAN_TYPE, new TimeSpanType().ParseResult(defaultValueFromConfig)), + TIMESPAN_TYPE => new(TIMESPAN_TYPE, new TimeSpanType().ParseResult(TimeSpan.Parse(defaultValueFromConfig))), BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))), _ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.") }; diff --git a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs index 13642ffa72..7e1861e4ea 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs @@ -53,7 +53,7 @@ public class StoredProcedureBuilderTests [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12:00:00 AM", false, DisplayName = "DateTime")] [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12000 AM", true, DisplayName = "DateTime")] [DataRow(typeof(DateTimeOffset), DATETIME_TYPE, "11/19/2012 10:57:11 AM -08:00", false, DisplayName = "DateTimeOffset")] - [DataRow(typeof(TimeSpan), TIMESPAN_TYPE, "10-57-11", false, DisplayName = "TimeSpan")] + [DataRow(typeof(TimeSpan), TIMESPAN_TYPE, "10:57:11.0000", false, DisplayName = "TimeSpan")] [DataRow(typeof(byte[]), BYTEARRAY_TYPE, "AgQGCAoMDhASFA==", false, DisplayName = "Byte[]")] public void StoredProcedure_ParameterValueTypeResolution( Type systemType, From f9ab1d5627c416a763a1c22ef797d8e33069fabd Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 8 May 2023 11:21:21 +0530 Subject: [PATCH 10/50] fixing MsSql tests --- .../SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs index 024e22f028..6a27c33046 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs @@ -31,7 +31,7 @@ public class MsSqlInsertApiTests : InsertApiTestBase "InsertOneInSupportedTypes", $"SELECT [id] as [typeid], [byte_types], [short_types], [int_types], [long_types],string_types, [single_types], [float_types], " + $"[decimal_types], [boolean_types], [date_types], [datetime_types], [datetime2_types], [datetimeoffset_types], [smalldatetime_types], " + - $"[bytearray_types], LOWER([guid_types]) as [guid_types] FROM { _integrationTypeTable } " + + $"[time_types], [bytearray_types], LOWER([guid_types]) as [guid_types] FROM { _integrationTypeTable } " + $"WHERE [id] = { STARTING_ID_FOR_TEST_INSERTS } AND [bytearray_types] is NULL " + $"FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER" }, From 1dd0f471192b0eefe4b9c1178de8a32bc4e53fbc Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Tue, 9 May 2023 01:12:24 -0700 Subject: [PATCH 11/50] Add timespan test in supported types --- .../GraphQLSupportedTypesTestsBase.cs | 11 +++++++++++ .../MySqlGQLSupportedTypesTests.cs | 1 + .../PostgreSqlGQLSupportedTypesTests.cs | 1 + 3 files changed, 13 insertions(+) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index 4da1fcaaf4..fb4021d322 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -59,6 +59,11 @@ public abstract class GraphQLSupportedTypesTestBase : SqlTestBase [DataRow(DATETIME_TYPE, 2)] [DataRow(DATETIME_TYPE, 3)] [DataRow(DATETIME_TYPE, 4)] + [DataRow(TIMESPAN_TYPE, 1)] + [DataRow(TIMESPAN_TYPE, 2)] + [DataRow(TIMESPAN_TYPE, 3)] + [DataRow(TIMESPAN_TYPE, 4)] + [DataRow(TIMESPAN_TYPE, 5)] [DataRow(BYTEARRAY_TYPE, 1)] [DataRow(BYTEARRAY_TYPE, 2)] [DataRow(BYTEARRAY_TYPE, 3)] @@ -121,6 +126,8 @@ public async Task QueryTypeColumn(string type, int id) [DataRow(DATETIME_TYPE, "\"1999-01-08 09:20:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08\"")] [DataRow(DATETIME_TYPE, "null")] + [DataRow(TIMESPAN_TYPE, "23:59:59.9999999")] + [DataRow(TIMESPAN_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "\"U3RyaW5neQ==\"")] [DataRow(BYTEARRAY_TYPE, "\"V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=\"")] [DataRow(BYTEARRAY_TYPE, "null")] @@ -163,6 +170,7 @@ public async Task InsertIntoTypeColumn(string type, string value) [DataRow(BOOLEAN_TYPE, true)] [DataRow(DATETIME_NONUTC_TYPE, "1999-01-08 10:23:54+8:00")] [DataRow(DATETIME_TYPE, "1999-01-08 10:23:54")] + [DataRow(TIMESPAN_TYPE, "23:59:59.9999999")] [DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")] public async Task InsertIntoTypeColumnWithArgument(string type, object value) { @@ -227,6 +235,8 @@ public async Task InsertIntoTypeColumnWithArgument(string type, object value) [DataRow(DATETIME_TYPE, "\"1999-01-08 09:20:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08\"")] [DataRow(DATETIME_TYPE, "null")] + [DataRow(TIMESPAN_TYPE, "23:59:59.9999999")] + [DataRow(TIMESPAN_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "\"U3RyaW5neQ==\"")] [DataRow(BYTEARRAY_TYPE, "\"V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=\"")] [DataRow(BYTEARRAY_TYPE, "null")] @@ -271,6 +281,7 @@ public async Task UpdateTypeColumn(string type, string value) [DataRow(BOOLEAN_TYPE, true)] [DataRow(DATETIME_TYPE, "1999-01-08 10:23:54")] [DataRow(DATETIME_NONUTC_TYPE, "1999-01-08 10:23:54+8:00")] + [DataRow(TIMESPAN_TYPE, "23:59:59.9999999")] [DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")] [DataRow(GUID_TYPE, "3a1483a5-9ac2-4998-bcf3-78a28078c6ac")] [DataRow(GUID_TYPE, null)] diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs index 18d6d886a0..0b312a8633 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs @@ -42,6 +42,7 @@ protected override bool IsSupportedType(string type) return type switch { GUID_TYPE => false, + TIMESPAN_TYPE => true, _ => true }; } diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs index 714cea5b89..aee31417b6 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs @@ -42,6 +42,7 @@ protected override bool IsSupportedType(string type) { BYTE_TYPE => false, DATETIME_NONUTC_TYPE => false, + TIMESPAN_TYPE => false, _ => true }; } From cd9ec5a1986f1a80b826e396549205ff59e70610 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Tue, 9 May 2023 01:31:42 -0700 Subject: [PATCH 12/50] Fix timespan_types column name --- src/Service.GraphQLBuilder/Sql/SchemaConverter.cs | 2 +- src/Service.Tests/DatabaseSchema-MsSql.sql | 4 ++-- .../SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs index 47fccc9033..9447688a2b 100644 --- a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs +++ b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs @@ -263,7 +263,7 @@ public static IValueNode CreateValueNodeFromDbObjectMetadata(object metadataValu decimal value => new ObjectValueNode(new ObjectFieldNode(DECIMAL_TYPE, new FloatValueNode(value))), DateTimeOffset value => new ObjectValueNode(new ObjectFieldNode(DATETIME_TYPE, new DateTimeType().ParseValue(value))), DateTime value => new ObjectValueNode(new ObjectFieldNode(DATETIME_TYPE, new DateTimeType().ParseResult(value))), - TimeSpan value => new ObjectValueNode(new ObjectFieldNode(TIMESPAN_TYPE, new TimeSpanType().ParseValue(value))), + TimeSpan value => new ObjectValueNode(new ObjectFieldNode(TIMESPAN_TYPE, new TimeSpanType().ParseResult(value))), byte[] value => new ObjectValueNode(new ObjectFieldNode(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(value))), _ => throw new DataApiBuilderException( message: $"The type {metadataValue.GetType()} is not supported as a GraphQL default value", diff --git a/src/Service.Tests/DatabaseSchema-MsSql.sql b/src/Service.Tests/DatabaseSchema-MsSql.sql index 4768723a12..1d911c27ce 100644 --- a/src/Service.Tests/DatabaseSchema-MsSql.sql +++ b/src/Service.Tests/DatabaseSchema-MsSql.sql @@ -162,7 +162,7 @@ CREATE TABLE type_table( datetime2_types datetime2, datetimeoffset_types datetimeoffset, smalldatetime_types smalldatetime, - time_types time, + timespan_types time, bytearray_types varbinary(max), guid_types uniqueidentifier DEFAULT newid() ); @@ -399,7 +399,7 @@ byte_types, short_types, int_types, long_types, string_types, single_types, float_types, decimal_types, boolean_types, -date_types, datetime_types, datetime2_types, datetimeoffset_types, smalldatetime_types, time_types, +date_types, datetime_types, datetime2_types, datetimeoffset_types, smalldatetime_types, timespan_types, bytearray_types) VALUES (1, 1, 1, 1, 1, '', 0.33, 0.33, 0.333333, 1, diff --git a/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs index 6a27c33046..ee3e6baa0b 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs @@ -31,7 +31,7 @@ public class MsSqlInsertApiTests : InsertApiTestBase "InsertOneInSupportedTypes", $"SELECT [id] as [typeid], [byte_types], [short_types], [int_types], [long_types],string_types, [single_types], [float_types], " + $"[decimal_types], [boolean_types], [date_types], [datetime_types], [datetime2_types], [datetimeoffset_types], [smalldatetime_types], " + - $"[time_types], [bytearray_types], LOWER([guid_types]) as [guid_types] FROM { _integrationTypeTable } " + + $"[timespan_types], [bytearray_types], LOWER([guid_types]) as [guid_types] FROM { _integrationTypeTable } " + $"WHERE [id] = { STARTING_ID_FOR_TEST_INSERTS } AND [bytearray_types] is NULL " + $"FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER" }, From e80ef05ca67f058797117c726260f3080cefaae4 Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Thu, 11 May 2023 14:27:41 -0700 Subject: [PATCH 13/50] Fix comparison of timespan data type --- .../GraphQLSupportedTypesTestsBase.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index fb4021d322..b1eca5190a 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -329,7 +329,10 @@ private static void PerformTestEqualsForExtendedTypes(string type, string expect { CompareDateTimeResults(actual.ToString(), expected); } - else + else if (type == TIMESPAN_TYPE) + { + CompareTimeSpanResults(actual.ToString(), expected); + } { SqlTestHelper.PerformTestEqualJsonStrings(expected, actual.ToString()); } @@ -400,6 +403,30 @@ private static void CompareDateTimeResults(string actual, string expected) } } + /// + /// Required due to different format between SQL time and HotChocolate TimeSpan time(ISO-8601) result. + /// + private static void CompareTimeSpanResults(string actual, string expected) + { + string fieldName = "timespan_types"; + + using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); + using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); + + string actualDateTime = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); + string expectedDateTime = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); + + // handles cases when one of the values is null + if (string.IsNullOrEmpty(actualDateTime) || string.IsNullOrEmpty(expectedDateTime)) + { + Assert.AreEqual(expectedDateTime, actualDateTime); + } + else + { + Assert.AreEqual(TimeSpan.Parse(expectedDateTime), TimeSpan.Parse(actualDateTime)); + } + } + /// /// Needed to map the type name to a graphql type in argument tests /// where the argument type need to be specified. From 1aa7b654a8e6abeea8e775037e3ca227746dd33c Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Thu, 11 May 2023 14:45:18 -0700 Subject: [PATCH 14/50] Missing else --- .../GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index b1eca5190a..3ded865007 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -333,6 +333,7 @@ private static void PerformTestEqualsForExtendedTypes(string type, string expect { CompareTimeSpanResults(actual.ToString(), expected); } + else { SqlTestHelper.PerformTestEqualJsonStrings(expected, actual.ToString()); } From d5d97fdf67ef3507c50bc10939020c0852d1a9ad Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 6 Jul 2023 12:36:46 +0530 Subject: [PATCH 15/50] adding timespan type --- src/Service/Services/TypeHelper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Service/Services/TypeHelper.cs b/src/Service/Services/TypeHelper.cs index 7f3d8498f9..b586e64379 100644 --- a/src/Service/Services/TypeHelper.cs +++ b/src/Service/Services/TypeHelper.cs @@ -31,6 +31,7 @@ public static class TypeHelper [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, [typeof(byte[])] = DbType.Binary, + [typeof(TimeSpan)] = DbType.Time, [typeof(byte?)] = DbType.Byte, [typeof(sbyte?)] = DbType.SByte, [typeof(short?)] = DbType.Int16, @@ -45,6 +46,7 @@ public static class TypeHelper [typeof(bool?)] = DbType.Boolean, [typeof(char?)] = DbType.StringFixedLength, [typeof(Guid?)] = DbType.Guid, + [typeof(TimeSpan?)] = DbType.Time, [typeof(object)] = DbType.Object }; From 09e85b36cd04bc95aebd5ac00ddae7123226880d Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 6 Jul 2023 14:12:04 +0530 Subject: [PATCH 16/50] fix formatting --- .../Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs | 2 -- src/Core/Services/ResolverMiddleware.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs b/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs index 1cced2ab41..b8b2e341c6 100644 --- a/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs +++ b/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs @@ -3,8 +3,6 @@ using System.Data; using System.Globalization; -using System.IO; -using System.Linq; using System.Net; using Azure.DataApiBuilder.Auth; using Azure.DataApiBuilder.Config.DatabasePrimitives; diff --git a/src/Core/Services/ResolverMiddleware.cs b/src/Core/Services/ResolverMiddleware.cs index ca29abbea1..51be5ad48f 100644 --- a/src/Core/Services/ResolverMiddleware.cs +++ b/src/Core/Services/ResolverMiddleware.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Globalization; using System.Text.Json; using Azure.DataApiBuilder.Core.Authorization; From 3c4eba3fe9607277e6812012c65008ab1bc8ca86 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 17 Jul 2023 10:00:54 +0530 Subject: [PATCH 17/50] handling nullable types --- src/Core/Services/TypeHelper.cs | 26 ++++++++----------- .../CLRtoJsonValueTypeUnitTests.cs | 5 ++-- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index 2f15a15dd4..d6ab299637 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -35,22 +35,9 @@ public static class TypeHelper [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, [typeof(byte[])] = DbType.Binary, + [typeof(DateTime)] = DbType.DateTime, + [typeof(DateTimeOffset)] = DbType.DateTimeOffset, [typeof(TimeSpan)] = DbType.Time, - [typeof(byte?)] = DbType.Byte, - [typeof(sbyte?)] = DbType.SByte, - [typeof(short?)] = DbType.Int16, - [typeof(ushort?)] = DbType.UInt16, - [typeof(int?)] = DbType.Int32, - [typeof(uint?)] = DbType.UInt32, - [typeof(long?)] = DbType.Int64, - [typeof(ulong?)] = DbType.UInt64, - [typeof(float?)] = DbType.Single, - [typeof(double?)] = DbType.Double, - [typeof(decimal?)] = DbType.Decimal, - [typeof(bool?)] = DbType.Boolean, - [typeof(char?)] = DbType.StringFixedLength, - [typeof(Guid?)] = DbType.Guid, - [typeof(TimeSpan?)] = DbType.Time, [typeof(object)] = DbType.Object }; @@ -153,6 +140,15 @@ public static JsonDataType GetJsonDataTypeFromSystemType(Type type) /// DbType for the given system type. Null when no mapping exists. public static DbType? GetDbTypeFromSystemType(Type systemType) { + // Get the underlying type argument if the 'systemType' argument is a nullable type. + Type? nullableUnderlyingType = Nullable.GetUnderlyingType(systemType); + + // Will not be null when the input argument 'systemType' is a closed generic nullable type. + if (nullableUnderlyingType is not null) + { + systemType = nullableUnderlyingType; + } + if (!_systemTypeToDbTypeMap.TryGetValue(systemType, out DbType dbType)) { return null; diff --git a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs index 7d497ed8ca..9027ba39ba 100644 --- a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs +++ b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs @@ -75,8 +75,8 @@ private static IEnumerable GetTestData_SupportedSystemTypesMapToJsonVa /// /// Validates the behavior of TypeHelper.GetJsonDataTypeFromSystemType(Type type) by /// ensuring that a nullable value type like int? is resolved to its underlying type int. - /// Consequently, the lookup in the _systemTypeToJsonDataTypeMap dictionary succeeds without - /// requiring nullable value type be defined as keys. + /// Consequently, the lookup in the _systemTypeToJsonDataTypeMap and _systemTypeToDbTypeMap + /// dictionary succeeds without requiring nullable value type be defined as keys. /// Nullable value types are represented in runtime as Nullable. Whereas /// nullable reference types do no have a standalone runtime representation. /// See csharplang discussion on why typeof(string?) (nullable reference type) is not valid, @@ -106,5 +106,6 @@ private static IEnumerable GetTestData_SupportedSystemTypesMapToJsonVa public void ResolveUnderlyingTypeForNullableValueType(Type nullableType) { Assert.AreNotEqual(notExpected: JsonDataType.Undefined, actual: TypeHelper.GetJsonDataTypeFromSystemType(nullableType)); + Assert.IsNotNull(TypeHelper.GetDbTypeFromSystemType(nullableType)); } } From 507802e01b5c3fd3c646201f9b885a1f1169780d Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 17 Jul 2023 11:10:56 +0530 Subject: [PATCH 18/50] resolving nits --- src/Core/Parsers/EdmModelBuilder.cs | 38 +---------------------------- src/Core/Services/TypeHelper.cs | 37 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/Core/Parsers/EdmModelBuilder.cs b/src/Core/Parsers/EdmModelBuilder.cs index 664fed928c..d895c8391b 100644 --- a/src/Core/Parsers/EdmModelBuilder.cs +++ b/src/Core/Parsers/EdmModelBuilder.cs @@ -69,7 +69,7 @@ SourceDefinition sourceDefinition { Type columnSystemType = sourceDefinition.Columns[column].SystemType; // need to convert our column system type to an Edm type - EdmPrimitiveTypeKind type = GetEdmPrimitiveTypeFromSystemType(columnSystemType); + EdmPrimitiveTypeKind type = TypeHelper.GetEdmPrimitiveTypeFromSystemType(columnSystemType); // The mapped (aliased) field name defined in the runtime config is used to create a representative // OData StructuralProperty. The created property is then added to the EdmEntityType. @@ -96,42 +96,6 @@ SourceDefinition sourceDefinition return this; } - /// - /// Given the system type, returns the corresponding primitive type kind. - /// - /// Type of the column. - /// EdmPrimitiveTypeKind - /// Throws when the column - private static EdmPrimitiveTypeKind GetEdmPrimitiveTypeFromSystemType(Type columnSystemType) - { - if (columnSystemType.IsArray) - { - columnSystemType = columnSystemType.GetElementType()!; - } - - EdmPrimitiveTypeKind type = columnSystemType.Name switch - { - "String" => EdmPrimitiveTypeKind.String, - "Guid" => EdmPrimitiveTypeKind.Guid, - "Byte" => EdmPrimitiveTypeKind.Byte, - "Int16" => EdmPrimitiveTypeKind.Int16, - "Int32" => EdmPrimitiveTypeKind.Int32, - "Int64" => EdmPrimitiveTypeKind.Int64, - "Single" => EdmPrimitiveTypeKind.Single, - "Double" => EdmPrimitiveTypeKind.Double, - "Decimal" => EdmPrimitiveTypeKind.Decimal, - "Boolean" => EdmPrimitiveTypeKind.Boolean, - "DateTime" => EdmPrimitiveTypeKind.DateTimeOffset, - "DateTimeOffset" => EdmPrimitiveTypeKind.DateTimeOffset, - "Date" => EdmPrimitiveTypeKind.Date, - "TimeSpan" => EdmPrimitiveTypeKind.TimeOfDay, - _ => throw new ArgumentException($"Column type" + - $" {columnSystemType.Name} not yet supported.") - }; - - return type; - } - /// /// Add the entity sets contained within the schema to container. /// diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index d6ab299637..aecf8df4ad 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -5,6 +5,7 @@ using System.Net; using Azure.DataApiBuilder.Core.Services.OpenAPI; using Azure.DataApiBuilder.Service.Exceptions; +using Microsoft.OData.Edm; namespace Azure.DataApiBuilder.Core.Services { @@ -105,6 +106,42 @@ public static class TypeHelper [SqlDbType.VarChar] = typeof(string) }; + /// + /// Given the system type, returns the corresponding primitive type kind. + /// + /// Type of the column. + /// EdmPrimitiveTypeKind + /// Throws when the column + public static EdmPrimitiveTypeKind GetEdmPrimitiveTypeFromSystemType(Type columnSystemType) + { + if (columnSystemType.IsArray) + { + columnSystemType = columnSystemType.GetElementType()!; + } + + EdmPrimitiveTypeKind type = columnSystemType.Name switch + { + "String" => EdmPrimitiveTypeKind.String, + "Guid" => EdmPrimitiveTypeKind.Guid, + "Byte" => EdmPrimitiveTypeKind.Byte, + "Int16" => EdmPrimitiveTypeKind.Int16, + "Int32" => EdmPrimitiveTypeKind.Int32, + "Int64" => EdmPrimitiveTypeKind.Int64, + "Single" => EdmPrimitiveTypeKind.Single, + "Double" => EdmPrimitiveTypeKind.Double, + "Decimal" => EdmPrimitiveTypeKind.Decimal, + "Boolean" => EdmPrimitiveTypeKind.Boolean, + "DateTime" => EdmPrimitiveTypeKind.DateTimeOffset, + "DateTimeOffset" => EdmPrimitiveTypeKind.DateTimeOffset, + "Date" => EdmPrimitiveTypeKind.Date, + "TimeSpan" => EdmPrimitiveTypeKind.TimeOfDay, + _ => throw new ArgumentException($"Column type" + + $" {columnSystemType.Name} not yet supported.") + }; + + return type; + } + /// /// Converts the .NET Framework (System/CLR) type to JsonDataType. /// Primitive data types in the OpenAPI standard (OAS) are based on the types supported From d852138fc24e7298306c42c0992a8c2d858fb6aa Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 18 Jul 2023 15:56:06 +0530 Subject: [PATCH 19/50] fixing sql type --- .../Sql Query Structures/BaseSqlQueryStructure.cs | 2 +- src/Core/Services/ResolverMiddleware.cs | 2 +- src/Core/Services/TypeHelper.cs | 7 ++++--- src/Service.GraphQLBuilder/Sql/SchemaConverter.cs | 2 +- src/Service.Tests/DatabaseSchema-MsSql.sql | 4 ++-- .../GraphQLBuilder/Sql/SchemaConverterTests.cs | 2 +- .../GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs | 2 +- .../OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs | 2 +- .../GraphQLSupportedTypesTestsBase.cs | 6 +++--- .../SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs | 2 +- 10 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs b/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs index b8b2e341c6..07cd74910c 100644 --- a/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs +++ b/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs @@ -364,7 +364,7 @@ protected static object ParseParamAsSystemType(string param, Type systemType) "DateTime" => DateTimeOffset.Parse(param, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), "DateTimeOffset" => DateTimeOffset.Parse(param), "Date" => DateOnly.Parse(param), - "TimeSpan" => TimeSpan.Parse(param, DateTimeFormatInfo.InvariantInfo), + "TimeOnly" => TimeOnly.Parse(param), "Guid" => Guid.Parse(param), _ => throw new NotSupportedException($"{systemType.Name} is not supported") }; diff --git a/src/Core/Services/ResolverMiddleware.cs b/src/Core/Services/ResolverMiddleware.cs index 51be5ad48f..3368d81489 100644 --- a/src/Core/Services/ResolverMiddleware.cs +++ b/src/Core/Services/ResolverMiddleware.cs @@ -199,7 +199,7 @@ private static object PreParseLeaf(IMiddlewareContext context, string leafJson) SingleType => Single.Parse(leafJson), DateTimeType => DateTimeOffset.Parse(leafJson, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), ByteArrayType => Convert.FromBase64String(leafJson), - TimeSpanType => TimeSpan.Parse(leafJson, DateTimeFormatInfo.InvariantInfo), + TimeSpanType => TimeOnly.Parse(leafJson, DateTimeFormatInfo.InvariantInfo), _ => leafJson }; } diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index aecf8df4ad..cdfd99b37c 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -38,7 +38,7 @@ public static class TypeHelper [typeof(byte[])] = DbType.Binary, [typeof(DateTime)] = DbType.DateTime, [typeof(DateTimeOffset)] = DbType.DateTimeOffset, - [typeof(TimeSpan)] = DbType.Time, + [typeof(TimeOnly)] = DbType.Time, [typeof(object)] = DbType.Object }; @@ -67,6 +67,7 @@ public static class TypeHelper [typeof(Guid)] = JsonDataType.String, [typeof(byte[])] = JsonDataType.String, [typeof(TimeSpan)] = JsonDataType.String, + [typeof(TimeOnly)] = JsonDataType.String, [typeof(object)] = JsonDataType.Object, [typeof(DateTime)] = JsonDataType.String, [typeof(DateTimeOffset)] = JsonDataType.String @@ -98,7 +99,7 @@ public static class TypeHelper [SqlDbType.SmallInt] = typeof(short), [SqlDbType.SmallMoney] = typeof(decimal), [SqlDbType.Text] = typeof(string), - [SqlDbType.Time] = typeof(TimeSpan), + [SqlDbType.Time] = typeof(TimeOnly), [SqlDbType.Timestamp] = typeof(byte[]), [SqlDbType.TinyInt] = typeof(byte), [SqlDbType.UniqueIdentifier] = typeof(Guid), @@ -134,7 +135,7 @@ public static EdmPrimitiveTypeKind GetEdmPrimitiveTypeFromSystemType(Type column "DateTime" => EdmPrimitiveTypeKind.DateTimeOffset, "DateTimeOffset" => EdmPrimitiveTypeKind.DateTimeOffset, "Date" => EdmPrimitiveTypeKind.Date, - "TimeSpan" => EdmPrimitiveTypeKind.TimeOfDay, + "TimeOnly" => EdmPrimitiveTypeKind.TimeOfDay, _ => throw new ArgumentException($"Column type" + $" {columnSystemType.Name} not yet supported.") }; diff --git a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs index 8117518a62..b710781eed 100644 --- a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs +++ b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs @@ -231,7 +231,7 @@ public static string GetGraphQLTypeFromSystemType(Type type) "DateTime" => DATETIME_TYPE, "DateTimeOffset" => DATETIME_TYPE, "Byte[]" => BYTEARRAY_TYPE, - "TimeSpan" => TIMESPAN_TYPE, + "TimeOnly" => TIMESPAN_TYPE, _ => throw new DataApiBuilderException( message: $"Column type {type} not handled by case. Please add a case resolving {type} to the appropriate GraphQL type", statusCode: HttpStatusCode.InternalServerError, diff --git a/src/Service.Tests/DatabaseSchema-MsSql.sql b/src/Service.Tests/DatabaseSchema-MsSql.sql index 1d911c27ce..4768723a12 100644 --- a/src/Service.Tests/DatabaseSchema-MsSql.sql +++ b/src/Service.Tests/DatabaseSchema-MsSql.sql @@ -162,7 +162,7 @@ CREATE TABLE type_table( datetime2_types datetime2, datetimeoffset_types datetimeoffset, smalldatetime_types smalldatetime, - timespan_types time, + time_types time, bytearray_types varbinary(max), guid_types uniqueidentifier DEFAULT newid() ); @@ -399,7 +399,7 @@ byte_types, short_types, int_types, long_types, string_types, single_types, float_types, decimal_types, boolean_types, -date_types, datetime_types, datetime2_types, datetimeoffset_types, smalldatetime_types, timespan_types, +date_types, datetime_types, datetime2_types, datetimeoffset_types, smalldatetime_types, time_types, bytearray_types) VALUES (1, 1, 1, 1, 1, '', 0.33, 0.33, 0.333333, 1, diff --git a/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs index fbc3dde454..0ef7d929d4 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs @@ -230,7 +230,7 @@ public void MultipleColumnsAllMapped() [DataRow(typeof(DateTimeOffset), DATETIME_TYPE)] [DataRow(typeof(byte[]), BYTEARRAY_TYPE)] [DataRow(typeof(Guid), STRING_TYPE)] - [DataRow(typeof(TimeSpan), TIMESPAN_TYPE)] + [DataRow(typeof(TimeOnly), TIMESPAN_TYPE)] public void SystemTypeMapsToCorrectGraphQLType(Type systemType, string graphQLType) { SourceDefinition table = new(); diff --git a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs index 3e3846b8bd..211e0127c3 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs @@ -54,7 +54,7 @@ public class StoredProcedureBuilderTests [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12:00:00 AM", false, DisplayName = "DateTime")] [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12000 AM", true, DisplayName = "DateTime")] [DataRow(typeof(DateTimeOffset), DATETIME_TYPE, "11/19/2012 10:57:11 AM -08:00", false, DisplayName = "DateTimeOffset")] - [DataRow(typeof(TimeSpan), TIMESPAN_TYPE, "10:57:11.0000", false, DisplayName = "TimeSpan")] + [DataRow(typeof(TimeOnly), TIMESPAN_TYPE, "10:57:11.0000", false, DisplayName = "TimeOnly")] [DataRow(typeof(byte[]), BYTEARRAY_TYPE, "AgQGCAoMDhASFA==", false, DisplayName = "Byte[]")] public void StoredProcedure_ParameterValueTypeResolution( Type systemType, diff --git a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs index 9027ba39ba..fc68b4bb14 100644 --- a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs +++ b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs @@ -99,7 +99,7 @@ private static IEnumerable GetTestData_SupportedSystemTypesMapToJsonVa [DataRow(typeof(bool?))] [DataRow(typeof(char?))] [DataRow(typeof(Guid?))] - [DataRow(typeof(TimeSpan?))] + [DataRow(typeof(TimeOnly?))] [DataRow(typeof(DateTime?))] [DataRow(typeof(DateTimeOffset?))] [DataTestMethod] diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index 3ded865007..3f4457dab7 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -331,7 +331,7 @@ private static void PerformTestEqualsForExtendedTypes(string type, string expect } else if (type == TIMESPAN_TYPE) { - CompareTimeSpanResults(actual.ToString(), expected); + CompareTimeResults(actual.ToString(), expected); } else { @@ -407,9 +407,9 @@ private static void CompareDateTimeResults(string actual, string expected) /// /// Required due to different format between SQL time and HotChocolate TimeSpan time(ISO-8601) result. /// - private static void CompareTimeSpanResults(string actual, string expected) + private static void CompareTimeResults(string actual, string expected) { - string fieldName = "timespan_types"; + string fieldName = "time_types"; using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); diff --git a/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs index 44f165174e..ec15851c6e 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs @@ -32,7 +32,7 @@ public class MsSqlInsertApiTests : InsertApiTestBase "InsertOneInSupportedTypes", $"SELECT [id] as [typeid], [byte_types], [short_types], [int_types], [long_types],string_types, [single_types], [float_types], " + $"[decimal_types], [boolean_types], [date_types], [datetime_types], [datetime2_types], [datetimeoffset_types], [smalldatetime_types], " + - $"[timespan_types], [bytearray_types], LOWER([guid_types]) as [guid_types] FROM { _integrationTypeTable } " + + $"[time_types], [bytearray_types], LOWER([guid_types]) as [guid_types] FROM { _integrationTypeTable } " + $"WHERE [id] = { STARTING_ID_FOR_TEST_INSERTS } AND [bytearray_types] is NULL " + $"FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER" }, From b84874f386cc3dace9d9f350ac8a30b2c721123f Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 20 Jul 2023 11:16:29 +0530 Subject: [PATCH 20/50] Add TimeOnly format for HotChocolate --- src/Core/Services/ResolverMiddleware.cs | 3 +- src/Core/Services/TypeHelper.cs | 1 + .../GraphQLTypes/DefaultValueType.cs | 2 +- .../GraphQLTypes/SupportedTypes.cs | 2 +- src/Service.GraphQLBuilder/GraphQLUtils.cs | 5 +- .../Queries/StandardQueryInputs.cs | 21 +++--- .../Sql/SchemaConverter.cs | 6 +- src/Service.Tests/DatabaseSchema-MsSql.sql | 4 +- .../Sql/SchemaConverterTests.cs | 2 +- .../Sql/StoredProcedureBuilderTests.cs | 2 +- .../GraphQLSupportedTypesTestsBase.cs | 72 ++++++++++++++----- .../MySqlGQLSupportedTypesTests.cs | 2 +- .../PostgreSqlGQLSupportedTypesTests.cs | 2 +- src/Service.Tests/dab-config.MsSql.json | 2 +- 14 files changed, 83 insertions(+), 43 deletions(-) diff --git a/src/Core/Services/ResolverMiddleware.cs b/src/Core/Services/ResolverMiddleware.cs index 3368d81489..2b115459c4 100644 --- a/src/Core/Services/ResolverMiddleware.cs +++ b/src/Core/Services/ResolverMiddleware.cs @@ -7,6 +7,7 @@ using Azure.DataApiBuilder.Core.Models; using Azure.DataApiBuilder.Core.Resolvers; using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars; +using Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes; using HotChocolate.Execution; using HotChocolate.Language; using HotChocolate.Resolvers; @@ -199,7 +200,7 @@ private static object PreParseLeaf(IMiddlewareContext context, string leafJson) SingleType => Single.Parse(leafJson), DateTimeType => DateTimeOffset.Parse(leafJson, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), ByteArrayType => Convert.FromBase64String(leafJson), - TimeSpanType => TimeOnly.Parse(leafJson, DateTimeFormatInfo.InvariantInfo), + TimeOnlyType => TimeOnly.Parse(leafJson, DateTimeFormatInfo.InvariantInfo), _ => leafJson }; } diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index cdfd99b37c..9f647c9523 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -136,6 +136,7 @@ public static EdmPrimitiveTypeKind GetEdmPrimitiveTypeFromSystemType(Type column "DateTimeOffset" => EdmPrimitiveTypeKind.DateTimeOffset, "Date" => EdmPrimitiveTypeKind.Date, "TimeOnly" => EdmPrimitiveTypeKind.TimeOfDay, + "TimeSpan" => EdmPrimitiveTypeKind.TimeOfDay, _ => throw new ArgumentException($"Column type" + $" {columnSystemType.Name} not yet supported.") }; diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs b/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs index 1a97f76a81..f17628ef7d 100644 --- a/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs +++ b/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs @@ -24,7 +24,7 @@ protected override void Configure(IInputObjectTypeDescriptor descriptor) descriptor.Field(DECIMAL_TYPE).Type(); descriptor.Field(DATETIME_TYPE).Type(); descriptor.Field(BYTEARRAY_TYPE).Type(); - descriptor.Field(TIMESPAN_TYPE).Type(); + descriptor.Field(TIMEONLY_TYPE).Type(); } } } diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs b/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs index 54b87936b5..fe50f4efe9 100644 --- a/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs +++ b/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs @@ -22,7 +22,7 @@ public static class SupportedTypes // since PostgreSQL doesn't support datetime values with a non-UTC time zone. public const string DATETIME_NONUTC_TYPE = "DateTimeNonUTC"; public const string BYTEARRAY_TYPE = "ByteArray"; - public const string TIMESPAN_TYPE = "TimeSpan"; + public const string TIMEONLY_TYPE = "TimeOnly"; public const string GUID_TYPE = "Guid"; } } diff --git a/src/Service.GraphQLBuilder/GraphQLUtils.cs b/src/Service.GraphQLBuilder/GraphQLUtils.cs index 93dcc491bf..312a6689e9 100644 --- a/src/Service.GraphQLBuilder/GraphQLUtils.cs +++ b/src/Service.GraphQLBuilder/GraphQLUtils.cs @@ -9,6 +9,7 @@ using Azure.DataApiBuilder.Service.Exceptions; using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars; using Azure.DataApiBuilder.Service.GraphQLBuilder.Directives; +using Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes; using Azure.DataApiBuilder.Service.GraphQLBuilder.Sql; using HotChocolate.Language; using HotChocolate.Types; @@ -54,7 +55,7 @@ public static bool IsBuiltInType(ITypeNode typeNode) BOOLEAN_TYPE, DATETIME_TYPE, BYTEARRAY_TYPE, - TIMESPAN_TYPE + TIMEONLY_TYPE }; string name = typeNode.NamedType().Name.Value; return inBuiltTypes.Contains(name); @@ -243,7 +244,7 @@ public static Tuple ConvertValueToGraphQLType(string default 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))), - TIMESPAN_TYPE => new(TIMESPAN_TYPE, new TimeSpanType().ParseResult(TimeSpan.Parse(defaultValueFromConfig))), + TIMEONLY_TYPE => new(TIMEONLY_TYPE, new TimeOnlyType().ParseResult(TimeOnly.Parse(defaultValueFromConfig))), BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))), _ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.") }; diff --git a/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs b/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs index 190c0d2c52..5411a782a1 100644 --- a/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs +++ b/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars; +using Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes; using HotChocolate.Language; using HotChocolate.Types; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedTypes; @@ -190,19 +191,19 @@ public static InputObjectTypeDefinitionNode DateTimeInputType() => } ); - public static InputObjectTypeDefinitionNode TimeSpanInputType() => + public static InputObjectTypeDefinitionNode TimeOnlyInputType() => new( location: null, - new NameNode("TimeSpanFilterInput"), - new StringValueNode("Input type for adding TimeSpan filters"), + new NameNode("TimeOnlyFilterInput"), + new StringValueNode("Input type for adding TimeOnly filters"), new List(), new List { - new InputValueDefinitionNode(null, new NameNode("eq"), new StringValueNode("Equals"), new TimeSpanType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("gt"), new StringValueNode("Greater Than"), new TimeSpanType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("gte"), new StringValueNode("Greater Than or Equal To"), new TimeSpanType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("lt"), new StringValueNode("Less Than"), new TimeSpanType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("lte"), new StringValueNode("Less Than or Equal To"), new TimeSpanType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("neq"), new StringValueNode("Not Equals"), new TimeSpanType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("eq"), new StringValueNode("Equals"), new TimeOnlyType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("gt"), new StringValueNode("Greater Than"), new TimeOnlyType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("gte"), new StringValueNode("Greater Than or Equal To"), new TimeOnlyType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("lt"), new StringValueNode("Less Than"), new TimeOnlyType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("lte"), new StringValueNode("Less Than or Equal To"), new TimeOnlyType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("neq"), new StringValueNode("Not Equals"), new TimeOnlyType().ToTypeNode(), null, new List()), new InputValueDefinitionNode(null, new NameNode("isNull"), new StringValueNode("is null test"), new BooleanType().ToTypeNode(), null, new List()) } ); @@ -232,7 +233,7 @@ public static InputObjectTypeDefinitionNode ByteArrayInputType() => { STRING_TYPE, StringInputType() }, { DATETIME_TYPE, DateTimeInputType() }, { BYTEARRAY_TYPE, ByteArrayInputType() }, - { TIMESPAN_TYPE, TimeSpanInputType() }, + { TIMEONLY_TYPE, TimeOnlyInputType() }, }; /// diff --git a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs index b710781eed..5e963a6e34 100644 --- a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs +++ b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs @@ -9,6 +9,7 @@ using Azure.DataApiBuilder.Service.Exceptions; using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars; using Azure.DataApiBuilder.Service.GraphQLBuilder.Directives; +using Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes; using Azure.DataApiBuilder.Service.GraphQLBuilder.Queries; using HotChocolate.Language; using HotChocolate.Types; @@ -231,7 +232,8 @@ public static string GetGraphQLTypeFromSystemType(Type type) "DateTime" => DATETIME_TYPE, "DateTimeOffset" => DATETIME_TYPE, "Byte[]" => BYTEARRAY_TYPE, - "TimeOnly" => TIMESPAN_TYPE, + "TimeOnly" => TIMEONLY_TYPE, + "TimeSpan" => TIMEONLY_TYPE, _ => throw new DataApiBuilderException( message: $"Column type {type} not handled by case. Please add a case resolving {type} to the appropriate GraphQL type", statusCode: HttpStatusCode.InternalServerError, @@ -264,7 +266,7 @@ public static IValueNode CreateValueNodeFromDbObjectMetadata(object metadataValu decimal value => new ObjectValueNode(new ObjectFieldNode(DECIMAL_TYPE, new FloatValueNode(value))), DateTimeOffset value => new ObjectValueNode(new ObjectFieldNode(DATETIME_TYPE, new DateTimeType().ParseValue(value))), DateTime value => new ObjectValueNode(new ObjectFieldNode(DATETIME_TYPE, new DateTimeType().ParseResult(value))), - TimeSpan value => new ObjectValueNode(new ObjectFieldNode(TIMESPAN_TYPE, new TimeSpanType().ParseResult(value))), + TimeOnly value => new ObjectValueNode(new ObjectFieldNode(TIMEONLY_TYPE, new TimeOnlyType().ParseResult(value))), byte[] value => new ObjectValueNode(new ObjectFieldNode(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(value))), _ => throw new DataApiBuilderException( message: $"The type {metadataValue.GetType()} is not supported as a GraphQL default value", diff --git a/src/Service.Tests/DatabaseSchema-MsSql.sql b/src/Service.Tests/DatabaseSchema-MsSql.sql index 4768723a12..22129ceeb0 100644 --- a/src/Service.Tests/DatabaseSchema-MsSql.sql +++ b/src/Service.Tests/DatabaseSchema-MsSql.sql @@ -162,7 +162,7 @@ CREATE TABLE type_table( datetime2_types datetime2, datetimeoffset_types datetimeoffset, smalldatetime_types smalldatetime, - time_types time, + timeonly_types time, bytearray_types varbinary(max), guid_types uniqueidentifier DEFAULT newid() ); @@ -399,7 +399,7 @@ byte_types, short_types, int_types, long_types, string_types, single_types, float_types, decimal_types, boolean_types, -date_types, datetime_types, datetime2_types, datetimeoffset_types, smalldatetime_types, time_types, +date_types, datetime_types, datetime2_types, datetimeoffset_types, smalldatetime_types, timeonly_types, bytearray_types) VALUES (1, 1, 1, 1, 1, '', 0.33, 0.33, 0.333333, 1, diff --git a/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs index 0ef7d929d4..c310bdd749 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs @@ -230,7 +230,7 @@ public void MultipleColumnsAllMapped() [DataRow(typeof(DateTimeOffset), DATETIME_TYPE)] [DataRow(typeof(byte[]), BYTEARRAY_TYPE)] [DataRow(typeof(Guid), STRING_TYPE)] - [DataRow(typeof(TimeOnly), TIMESPAN_TYPE)] + [DataRow(typeof(TimeOnly), TIMEONLY_TYPE)] public void SystemTypeMapsToCorrectGraphQLType(Type systemType, string graphQLType) { SourceDefinition table = new(); diff --git a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs index 211e0127c3..7ed46419e7 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs @@ -54,7 +54,7 @@ public class StoredProcedureBuilderTests [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12:00:00 AM", false, DisplayName = "DateTime")] [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12000 AM", true, DisplayName = "DateTime")] [DataRow(typeof(DateTimeOffset), DATETIME_TYPE, "11/19/2012 10:57:11 AM -08:00", false, DisplayName = "DateTimeOffset")] - [DataRow(typeof(TimeOnly), TIMESPAN_TYPE, "10:57:11.0000", false, DisplayName = "TimeOnly")] + [DataRow(typeof(TimeOnly), TIMEONLY_TYPE, "10:57:11.0000", false, DisplayName = "TimeOnly")] [DataRow(typeof(byte[]), BYTEARRAY_TYPE, "AgQGCAoMDhASFA==", false, DisplayName = "Byte[]")] public void StoredProcedure_ParameterValueTypeResolution( Type systemType, diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index 3f4457dab7..6f904908ef 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text.Json; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -59,11 +60,11 @@ public abstract class GraphQLSupportedTypesTestBase : SqlTestBase [DataRow(DATETIME_TYPE, 2)] [DataRow(DATETIME_TYPE, 3)] [DataRow(DATETIME_TYPE, 4)] - [DataRow(TIMESPAN_TYPE, 1)] - [DataRow(TIMESPAN_TYPE, 2)] - [DataRow(TIMESPAN_TYPE, 3)] - [DataRow(TIMESPAN_TYPE, 4)] - [DataRow(TIMESPAN_TYPE, 5)] + [DataRow(TIMEONLY_TYPE, 1)] + [DataRow(TIMEONLY_TYPE, 2)] + [DataRow(TIMEONLY_TYPE, 3)] + [DataRow(TIMEONLY_TYPE, 4)] + [DataRow(TIMEONLY_TYPE, 5)] [DataRow(BYTEARRAY_TYPE, 1)] [DataRow(BYTEARRAY_TYPE, 2)] [DataRow(BYTEARRAY_TYPE, 3)] @@ -126,8 +127,8 @@ public async Task QueryTypeColumn(string type, int id) [DataRow(DATETIME_TYPE, "\"1999-01-08 09:20:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08\"")] [DataRow(DATETIME_TYPE, "null")] - [DataRow(TIMESPAN_TYPE, "23:59:59.9999999")] - [DataRow(TIMESPAN_TYPE, "null")] + [DataRow(TIMEONLY_TYPE, "23:59:59.9999999")] + [DataRow(TIMEONLY_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "\"U3RyaW5neQ==\"")] [DataRow(BYTEARRAY_TYPE, "\"V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=\"")] [DataRow(BYTEARRAY_TYPE, "null")] @@ -170,7 +171,7 @@ public async Task InsertIntoTypeColumn(string type, string value) [DataRow(BOOLEAN_TYPE, true)] [DataRow(DATETIME_NONUTC_TYPE, "1999-01-08 10:23:54+8:00")] [DataRow(DATETIME_TYPE, "1999-01-08 10:23:54")] - [DataRow(TIMESPAN_TYPE, "23:59:59.9999999")] + [DataRow(TIMEONLY_TYPE, "23:59:59.9999999")] [DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")] public async Task InsertIntoTypeColumnWithArgument(string type, object value) { @@ -235,8 +236,8 @@ public async Task InsertIntoTypeColumnWithArgument(string type, object value) [DataRow(DATETIME_TYPE, "\"1999-01-08 09:20:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08\"")] [DataRow(DATETIME_TYPE, "null")] - [DataRow(TIMESPAN_TYPE, "23:59:59.9999999")] - [DataRow(TIMESPAN_TYPE, "null")] + [DataRow(TIMEONLY_TYPE, "23:59:59.9999999")] + [DataRow(TIMEONLY_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "\"U3RyaW5neQ==\"")] [DataRow(BYTEARRAY_TYPE, "\"V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=\"")] [DataRow(BYTEARRAY_TYPE, "null")] @@ -281,7 +282,7 @@ public async Task UpdateTypeColumn(string type, string value) [DataRow(BOOLEAN_TYPE, true)] [DataRow(DATETIME_TYPE, "1999-01-08 10:23:54")] [DataRow(DATETIME_NONUTC_TYPE, "1999-01-08 10:23:54+8:00")] - [DataRow(TIMESPAN_TYPE, "23:59:59.9999999")] + [DataRow(TIMEONLY_TYPE, "23:59:59.9999999")] [DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")] [DataRow(GUID_TYPE, "3a1483a5-9ac2-4998-bcf3-78a28078c6ac")] [DataRow(GUID_TYPE, null)] @@ -329,7 +330,7 @@ private static void PerformTestEqualsForExtendedTypes(string type, string expect { CompareDateTimeResults(actual.ToString(), expected); } - else if (type == TIMESPAN_TYPE) + else if (type == TIMEONLY_TYPE) { CompareTimeResults(actual.ToString(), expected); } @@ -400,31 +401,64 @@ private static void CompareDateTimeResults(string actual, string expected) } else { - Assert.AreEqual(DateTimeOffset.Parse(expectedDateTime), DateTimeOffset.Parse(actualDateTime)); + // Adjusting to universal, since DateTime doesn't account for TimeZone + DateTime expectedDateTimeUniversal = DateTime.Parse(expectedDateTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + DateTime actualDateTimeUniversal = DateTime.Parse(actualDateTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + Assert.AreEqual(expectedDateTimeUniversal, actualDateTimeUniversal); } } + /// + /// Required due to different format between mysql datetimeoffset and HotChocolate datetime + /// result + /// + //private static void CompareDateTimeOffsetResults(string actual, string expected) + //{ + // string fieldName = "datetimeoffset_types"; + + // using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); + // using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); + + // string actualDateTime = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); + // string expectedDateTime = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); + + // // handles cases when one of the values is null + // if (string.IsNullOrEmpty(actualDateTime) || string.IsNullOrEmpty(expectedDateTime)) + // { + // Assert.AreEqual(expectedDateTime, actualDateTime); + // } + // else + // { + // // Adjusting to universal, since DateTime doesn't account for TimeZone + // DateTime expectedDateTimeUniversal = DateTimeOffset.Parse(expectedDateTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + // DateTime actualDateTimeUniversal = DateTimeOffset.Parse(actualDateTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + // Assert.AreEqual(expectedDateTimeUniversal, actualDateTimeUniversal); + // } + //} + /// /// Required due to different format between SQL time and HotChocolate TimeSpan time(ISO-8601) result. /// private static void CompareTimeResults(string actual, string expected) { - string fieldName = "time_types"; + string fieldName = "timeonly_types"; using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); - string actualDateTime = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); - string expectedDateTime = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); + string actualTimeString = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); + string expectedTimeString = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); // handles cases when one of the values is null - if (string.IsNullOrEmpty(actualDateTime) || string.IsNullOrEmpty(expectedDateTime)) + if (string.IsNullOrEmpty(actualTimeString) || string.IsNullOrEmpty(expectedTimeString)) { - Assert.AreEqual(expectedDateTime, actualDateTime); + Assert.AreEqual(expectedTimeString, actualTimeString); } else { - Assert.AreEqual(TimeSpan.Parse(expectedDateTime), TimeSpan.Parse(actualDateTime)); + TimeOnly actualTime = TimeOnly.Parse(actualTimeString, CultureInfo.InvariantCulture); + TimeOnly expectedTime = TimeOnly.Parse(expectedTimeString, CultureInfo.InvariantCulture); + Assert.AreEqual(expectedTime.ToLongTimeString(), actualTime.ToLongTimeString()); } } diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs index 0b312a8633..6a129a9cc2 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs @@ -42,7 +42,7 @@ protected override bool IsSupportedType(string type) return type switch { GUID_TYPE => false, - TIMESPAN_TYPE => true, + TIMEONLY_TYPE => true, _ => true }; } diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs index aee31417b6..63c36ee5c5 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs @@ -42,7 +42,7 @@ protected override bool IsSupportedType(string type) { BYTE_TYPE => false, DATETIME_NONUTC_TYPE => false, - TIMESPAN_TYPE => false, + TIMEONLY_TYPE => false, _ => true }; } diff --git a/src/Service.Tests/dab-config.MsSql.json b/src/Service.Tests/dab-config.MsSql.json index 3f6db5db9b..6b8b717c62 100644 --- a/src/Service.Tests/dab-config.MsSql.json +++ b/src/Service.Tests/dab-config.MsSql.json @@ -2,7 +2,7 @@ "$schema": "https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json", "data-source": { "database-type": "mssql", - "connection-string": "Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=sa;Password=REPLACEME;MultipleActiveResultSets=False;Connection Timeout=5;", + "connection-string": "Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=dabuser;Password=Adabpassword@1;TrustServerCertificate=True;MultipleActiveResultSets=False;Connection Timeout=5;", "options": { "set-session-context": true } From 9e1a93c521a69e2bbc8de8d9ba1a244719290096 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 20 Jul 2023 11:17:12 +0530 Subject: [PATCH 21/50] fix formatting --- .../GraphQLTypes/TimeOnlytype.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs b/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs new file mode 100644 index 0000000000..3de63e981a --- /dev/null +++ b/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using HotChocolate.Language; +using HotChocolate.Types; + +namespace Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes; +public class TimeOnlyType : ScalarType +{ + /// + /// Initializes a new instance of the class. + /// + public TimeOnlyType() : base("TimeOnly") + { } + + /// + public override bool IsInstanceOfType(IValueNode valueSyntax) + { + return valueSyntax is StringValueNode; + } + + /// + public override object ParseLiteral(IValueNode literal) + { + if (literal is StringValueNode stringValueNode) + { + if (TimeSpan.TryParse(stringValueNode.Value, out TimeSpan timeSpan)) + { + return timeSpan; + } + } + + throw new ArgumentException("Invalid TimeOnly value."); + } + + /// + public override IValueNode ParseResult(object? resultValue) + { + if (resultValue is TimeOnly timeSpan) + { + return new StringValueNode(timeSpan.ToString(@"hh\:mm\:ss\.fff")); + } + + throw new ArgumentException("Invalid TimeOnly value."); + } + + /// + public override IValueNode ParseValue(object? value) + { + if (value is TimeOnly timeSpan) + { + return new StringValueNode(timeSpan.ToString(@"hh\:mm\:ss\.fff")); + } + + throw new ArgumentException("Invalid TimeOnly value."); + } + /// + public override object Serialize(object? value) + { + if (value is TimeSpan timeSpan) + { + return timeSpan.ToString(@"hh\:mm\:ss\.fff"); + } + + throw new ArgumentException("Invalid TimeOnly value."); + } +} From 84e11e657a5352caa65c646593ddcaca55ffcdbd Mon Sep 17 00:00:00 2001 From: abhishekkumams <102276754+abhishekkumams@users.noreply.github.com> Date: Thu, 20 Jul 2023 11:18:33 +0530 Subject: [PATCH 22/50] Update dab-config.MsSql.json --- src/Service.Tests/dab-config.MsSql.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/dab-config.MsSql.json b/src/Service.Tests/dab-config.MsSql.json index 6b8b717c62..3f6db5db9b 100644 --- a/src/Service.Tests/dab-config.MsSql.json +++ b/src/Service.Tests/dab-config.MsSql.json @@ -2,7 +2,7 @@ "$schema": "https://github.com/Azure/data-api-builder/releases/download/vmajor.minor.patch/dab.draft.schema.json", "data-source": { "database-type": "mssql", - "connection-string": "Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=dabuser;Password=Adabpassword@1;TrustServerCertificate=True;MultipleActiveResultSets=False;Connection Timeout=5;", + "connection-string": "Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=sa;Password=REPLACEME;MultipleActiveResultSets=False;Connection Timeout=5;", "options": { "set-session-context": true } From 38a9e24f264570f27b43b4d64eea35d6324646ac Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 20 Jul 2023 11:24:29 +0530 Subject: [PATCH 23/50] fix test --- .../SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs index ec15851c6e..b8d1bd6d4b 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs @@ -32,7 +32,7 @@ public class MsSqlInsertApiTests : InsertApiTestBase "InsertOneInSupportedTypes", $"SELECT [id] as [typeid], [byte_types], [short_types], [int_types], [long_types],string_types, [single_types], [float_types], " + $"[decimal_types], [boolean_types], [date_types], [datetime_types], [datetime2_types], [datetimeoffset_types], [smalldatetime_types], " + - $"[time_types], [bytearray_types], LOWER([guid_types]) as [guid_types] FROM { _integrationTypeTable } " + + $"[timeonly_types], [bytearray_types], LOWER([guid_types]) as [guid_types] FROM { _integrationTypeTable } " + $"WHERE [id] = { STARTING_ID_FOR_TEST_INSERTS } AND [bytearray_types] is NULL " + $"FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER" }, From 718941b84ff29afdf902443c5d5ca18eb5b60b08 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 20 Jul 2023 12:20:28 +0530 Subject: [PATCH 24/50] fixing mysql tests --- .../GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs index 6a129a9cc2..9632690288 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs @@ -42,7 +42,7 @@ protected override bool IsSupportedType(string type) return type switch { GUID_TYPE => false, - TIMEONLY_TYPE => true, + TIMEONLY_TYPE => false, _ => true }; } From 910c2d7dddcc189fbf7bd8c2bfc08f357fc52148 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 21 Jul 2023 11:45:10 +0530 Subject: [PATCH 25/50] fixing tests --- .../BaseSqlQueryStructure.cs | 3 +- src/Core/Services/GraphQLSchemaCreator.cs | 1 + .../GraphQLTypes/SupportedTypes.cs | 2 +- .../GraphQLTypes/TimeOnlytype.cs | 5 +- .../GraphQLSupportedTypesTestsBase.cs | 91 +++++++------------ .../MySqlGQLSupportedTypesTests.cs | 1 + .../PostgreSqlGQLSupportedTypesTests.cs | 2 +- 7 files changed, 43 insertions(+), 62 deletions(-) diff --git a/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs b/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs index 07cd74910c..637c84eb4f 100644 --- a/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs +++ b/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs @@ -361,10 +361,11 @@ protected static object ParseParamAsSystemType(string param, Type systemType) "Double" => double.Parse(param), "Decimal" => decimal.Parse(param), "Boolean" => bool.Parse(param), - "DateTime" => DateTimeOffset.Parse(param, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), + "DateTime" => DateTime.Parse(param, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), "DateTimeOffset" => DateTimeOffset.Parse(param), "Date" => DateOnly.Parse(param), "TimeOnly" => TimeOnly.Parse(param), + "TimeSpan" => TimeOnly.Parse(param), "Guid" => Guid.Parse(param), _ => throw new NotSupportedException($"{systemType.Name} is not supported") }; diff --git a/src/Core/Services/GraphQLSchemaCreator.cs b/src/Core/Services/GraphQLSchemaCreator.cs index c8fb481e2b..93e5d27330 100644 --- a/src/Core/Services/GraphQLSchemaCreator.cs +++ b/src/Core/Services/GraphQLSchemaCreator.cs @@ -91,6 +91,7 @@ private ISchemaBuilder Parse( .AddDirectiveType() // Add our custom scalar GraphQL types .AddType() + .AddType() .AddType() // Generate the GraphQL queries from the provided objects .AddDocument(QueryBuilder.Build(root, _databaseType, _entities, inputTypes, _authorizationResolver.EntityPermissionsMap, dbObjects)) diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs b/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs index fe50f4efe9..cd0160a045 100644 --- a/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs +++ b/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs @@ -20,7 +20,7 @@ public static class SupportedTypes public const string DATETIME_TYPE = "DateTime"; // The DATETIME_NONUTC_TYPE constant is only used in testing // since PostgreSQL doesn't support datetime values with a non-UTC time zone. - public const string DATETIME_NONUTC_TYPE = "DateTimeNonUTC"; + public const string DATETIMEOFFSET_TYPE = "DateTimeOffset"; public const string BYTEARRAY_TYPE = "ByteArray"; public const string TIMEONLY_TYPE = "TimeOnly"; public const string GUID_TYPE = "Guid"; diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs b/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs index 3de63e981a..1ad7d218fa 100644 --- a/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs +++ b/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs @@ -36,9 +36,9 @@ public override object ParseLiteral(IValueNode literal) /// public override IValueNode ParseResult(object? resultValue) { - if (resultValue is TimeOnly timeSpan) + if (resultValue is TimeOnly timeOnly) { - return new StringValueNode(timeSpan.ToString(@"hh\:mm\:ss\.fff")); + return new StringValueNode(timeOnly.ToString(@"hh\:mm\:ss\.fff")); } throw new ArgumentException("Invalid TimeOnly value."); @@ -54,6 +54,7 @@ public override IValueNode ParseValue(object? value) throw new ArgumentException("Invalid TimeOnly value."); } + /// public override object Serialize(object? value) { diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index 6f904908ef..b52b1e5cce 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -123,11 +123,11 @@ public async Task QueryTypeColumn(string type, int id) [DataRow(BOOLEAN_TYPE, "true")] [DataRow(BOOLEAN_TYPE, "false")] [DataRow(BOOLEAN_TYPE, "null")] - [DataRow(DATETIME_NONUTC_TYPE, "\"1999-01-08 10:23:54+8:00\"")] + [DataRow(DATETIMEOFFSET_TYPE, "\"1999-01-08 10:23:54+8:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08 09:20:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08\"")] [DataRow(DATETIME_TYPE, "null")] - [DataRow(TIMEONLY_TYPE, "23:59:59.9999999")] + [DataRow(TIMEONLY_TYPE, "\"23:59:59.9999999\"")] [DataRow(TIMEONLY_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "\"U3RyaW5neQ==\"")] [DataRow(BYTEARRAY_TYPE, "\"V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=\"")] @@ -139,13 +139,6 @@ public async Task InsertIntoTypeColumn(string type, string value) Assert.Inconclusive("Type not supported"); } - // Datetime non utc type is a characterization of the value added to the datetime type, - // so before executing the query reset it to mean the actually underlying type. - if (DATETIME_NONUTC_TYPE.Equals(type)) - { - type = DATETIME_TYPE; - } - string field = $"{type.ToLowerInvariant()}_types"; string graphQLQueryName = "createSupportedType"; string gqlQuery = "mutation{ createSupportedType (item: {" + field + ": " + value + " }){ " + field + " } }"; @@ -169,7 +162,7 @@ public async Task InsertIntoTypeColumn(string type, string value) [DataRow(FLOAT_TYPE, -3.33)] [DataRow(DECIMAL_TYPE, 1222222.00000929292)] [DataRow(BOOLEAN_TYPE, true)] - [DataRow(DATETIME_NONUTC_TYPE, "1999-01-08 10:23:54+8:00")] + [DataRow(DATETIMEOFFSET_TYPE, "1999-01-08 10:23:54+8:00")] [DataRow(DATETIME_TYPE, "1999-01-08 10:23:54")] [DataRow(TIMEONLY_TYPE, "23:59:59.9999999")] [DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")] @@ -180,16 +173,9 @@ public async Task InsertIntoTypeColumnWithArgument(string type, object value) Assert.Inconclusive("Type not supported"); } - // Datetime non utc type is a characterization of the value added to the datetime type, - // so before executing the query reset it to mean the actually underlying type. - if (DATETIME_NONUTC_TYPE.Equals(type)) - { - type = DATETIME_TYPE; - } - string field = $"{type.ToLowerInvariant()}_types"; string graphQLQueryName = "createSupportedType"; - string gqlQuery = "mutation($param: " + type + "){ createSupportedType (item: {" + field + ": $param }){ " + field + " } }"; + string gqlQuery = "mutation($param: " + TypeNameToGraphQLType(type) + "){ createSupportedType (item: {" + field + ": $param }){ " + field + " } }"; string dbQuery = MakeQueryOnTypeTable(new List { field }, id: 5001); @@ -232,7 +218,7 @@ public async Task InsertIntoTypeColumnWithArgument(string type, object value) [DataRow(BOOLEAN_TYPE, "true")] [DataRow(BOOLEAN_TYPE, "false")] [DataRow(BOOLEAN_TYPE, "null")] - [DataRow(DATETIME_NONUTC_TYPE, "\"1999-01-08 10:23:54+8:00\"")] + [DataRow(DATETIMEOFFSET_TYPE, "\"1999-01-08 10:23:54+8:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08 09:20:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08\"")] [DataRow(DATETIME_TYPE, "null")] @@ -250,13 +236,6 @@ public async Task UpdateTypeColumn(string type, string value) Assert.Inconclusive("Type not supported"); } - // Datetime non utc type is a characterization of the value added to the datetime type, - // so before executing the query reset it to mean the actually underlying type. - if (DATETIME_NONUTC_TYPE.Equals(type)) - { - type = DATETIME_TYPE; - } - string field = $"{type.ToLowerInvariant()}_types"; string graphQLQueryName = "updateSupportedType"; string gqlQuery = "mutation{ updateSupportedType (typeid: 1, item: {" + field + ": " + value + " }){ " + field + " } }"; @@ -281,7 +260,7 @@ public async Task UpdateTypeColumn(string type, string value) [DataRow(DECIMAL_TYPE, 1222222.00000929292)] [DataRow(BOOLEAN_TYPE, true)] [DataRow(DATETIME_TYPE, "1999-01-08 10:23:54")] - [DataRow(DATETIME_NONUTC_TYPE, "1999-01-08 10:23:54+8:00")] + [DataRow(DATETIMEOFFSET_TYPE, "1999-01-08 10:23:54+8:00")] [DataRow(TIMEONLY_TYPE, "23:59:59.9999999")] [DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")] [DataRow(GUID_TYPE, "3a1483a5-9ac2-4998-bcf3-78a28078c6ac")] @@ -293,13 +272,6 @@ public async Task UpdateTypeColumnWithArgument(string type, object value) Assert.Inconclusive("Type not supported"); } - // Datetime non utc type is a characterization of the value added to the datetime type, - // so before executing the query reset it to mean the actually underlying type. - if (DATETIME_NONUTC_TYPE.Equals(type)) - { - type = DATETIME_TYPE; - } - string field = $"{type.ToLowerInvariant()}_types"; string graphQLQueryName = "updateSupportedType"; string gqlQuery = "mutation($param: " + TypeNameToGraphQLType(type) + "){ updateSupportedType (typeid: 1, item: {" + field + ": $param }){ " + field + " } }"; @@ -330,6 +302,10 @@ private static void PerformTestEqualsForExtendedTypes(string type, string expect { CompareDateTimeResults(actual.ToString(), expected); } + else if (type == DATETIMEOFFSET_TYPE) + { + CompareDateTimeOffsetResults(actual.ToString(), expected); + } else if (type == TIMEONLY_TYPE) { CompareTimeResults(actual.ToString(), expected); @@ -412,29 +388,26 @@ private static void CompareDateTimeResults(string actual, string expected) /// Required due to different format between mysql datetimeoffset and HotChocolate datetime /// result /// - //private static void CompareDateTimeOffsetResults(string actual, string expected) - //{ - // string fieldName = "datetimeoffset_types"; - - // using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); - // using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); - - // string actualDateTime = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); - // string expectedDateTime = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); - - // // handles cases when one of the values is null - // if (string.IsNullOrEmpty(actualDateTime) || string.IsNullOrEmpty(expectedDateTime)) - // { - // Assert.AreEqual(expectedDateTime, actualDateTime); - // } - // else - // { - // // Adjusting to universal, since DateTime doesn't account for TimeZone - // DateTime expectedDateTimeUniversal = DateTimeOffset.Parse(expectedDateTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); - // DateTime actualDateTimeUniversal = DateTimeOffset.Parse(actualDateTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); - // Assert.AreEqual(expectedDateTimeUniversal, actualDateTimeUniversal); - // } - //} + private static void CompareDateTimeOffsetResults(string actual, string expected) + { + string fieldName = "datetimeoffset_types"; + + using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); + using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); + + string actualDateTimeOffset = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); + string expectedDateTimeOffset = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); + + // handles cases when one of the values is null + if (string.IsNullOrEmpty(actualDateTimeOffset) || string.IsNullOrEmpty(expectedDateTimeOffset)) + { + Assert.AreEqual(expectedDateTimeOffset, actualDateTimeOffset); + } + else + { + Assert.AreEqual(DateTimeOffset.Parse(expectedDateTimeOffset), DateTimeOffset.Parse(actualDateTimeOffset)); + } + } /// /// Required due to different format between SQL time and HotChocolate TimeSpan time(ISO-8601) result. @@ -472,6 +445,10 @@ private static string TypeNameToGraphQLType(string typeName) { return STRING_TYPE; } + else if (typeName is DATETIMEOFFSET_TYPE) + { + return DATETIME_TYPE; + } return typeName; } diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs index 9632690288..b673e6350e 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs @@ -42,6 +42,7 @@ protected override bool IsSupportedType(string type) return type switch { GUID_TYPE => false, + DATETIMEOFFSET_TYPE => false, TIMEONLY_TYPE => false, _ => true }; diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs index 63c36ee5c5..8bfedb83d8 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs @@ -41,7 +41,7 @@ protected override bool IsSupportedType(string type) return type switch { BYTE_TYPE => false, - DATETIME_NONUTC_TYPE => false, + DATETIMEOFFSET_TYPE => false, TIMEONLY_TYPE => false, _ => true }; From 5e01c3cef0e57082c9262806ac4fcb293b2bf1be Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 21 Jul 2023 11:53:11 +0530 Subject: [PATCH 26/50] fix formatting --- .../GraphQLSupportedTypesTestsBase.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index b52b1e5cce..31a9250141 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -390,23 +390,23 @@ private static void CompareDateTimeResults(string actual, string expected) /// private static void CompareDateTimeOffsetResults(string actual, string expected) { - string fieldName = "datetimeoffset_types"; + string fieldName = "datetimeoffset_types"; - using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); - using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); + using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); + using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); - string actualDateTimeOffset = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); - string expectedDateTimeOffset = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); + string actualDateTimeOffset = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); + string expectedDateTimeOffset = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); - // handles cases when one of the values is null - if (string.IsNullOrEmpty(actualDateTimeOffset) || string.IsNullOrEmpty(expectedDateTimeOffset)) - { - Assert.AreEqual(expectedDateTimeOffset, actualDateTimeOffset); - } - else - { + // handles cases when one of the values is null + if (string.IsNullOrEmpty(actualDateTimeOffset) || string.IsNullOrEmpty(expectedDateTimeOffset)) + { + Assert.AreEqual(expectedDateTimeOffset, actualDateTimeOffset); + } + else + { Assert.AreEqual(DateTimeOffset.Parse(expectedDateTimeOffset), DateTimeOffset.Parse(actualDateTimeOffset)); - } + } } /// From fc0be0417c4a3e64a47df099618a88f0c98b2f3a Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 24 Jul 2023 16:12:18 +0530 Subject: [PATCH 27/50] fixing tests --- src/Core/Services/GraphQLSchemaCreator.cs | 1 - src/Core/Services/TypeHelper.cs | 1 + src/Directory.Packages.props | 2 +- .../GraphQLTypes/TimeOnlytype.cs | 16 +++++++------- .../GraphQLFilterTestBase.cs | 22 +++++++++++++++++++ .../GraphQLFilterTests/MsSqlGQLFilterTests.cs | 14 ++++++++++++ 6 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/Core/Services/GraphQLSchemaCreator.cs b/src/Core/Services/GraphQLSchemaCreator.cs index 93e5d27330..c8fb481e2b 100644 --- a/src/Core/Services/GraphQLSchemaCreator.cs +++ b/src/Core/Services/GraphQLSchemaCreator.cs @@ -91,7 +91,6 @@ private ISchemaBuilder Parse( .AddDirectiveType() // Add our custom scalar GraphQL types .AddType() - .AddType() .AddType() // Generate the GraphQL queries from the provided objects .AddDocument(QueryBuilder.Build(root, _databaseType, _entities, inputTypes, _authorizationResolver.EntityPermissionsMap, dbObjects)) diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index 9f647c9523..cf31bb2434 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -39,6 +39,7 @@ public static class TypeHelper [typeof(DateTime)] = DbType.DateTime, [typeof(DateTimeOffset)] = DbType.DateTimeOffset, [typeof(TimeOnly)] = DbType.Time, + [typeof(TimeSpan)] = DbType.Time, [typeof(object)] = DbType.Object }; diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index dde5860283..1e1c985184 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -19,7 +19,7 @@ - + diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs b/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs index 1ad7d218fa..e20b15be55 100644 --- a/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs +++ b/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs @@ -5,7 +5,7 @@ using HotChocolate.Types; namespace Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes; -public class TimeOnlyType : ScalarType +public class TimeOnlyType : ScalarType { /// /// Initializes a new instance of the class. @@ -26,11 +26,11 @@ public override object ParseLiteral(IValueNode literal) { if (TimeSpan.TryParse(stringValueNode.Value, out TimeSpan timeSpan)) { - return timeSpan; + return new TimeOnly(timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds, timeSpan.Milliseconds); } } - throw new ArgumentException("Invalid TimeOnly value."); + throw new ArgumentException("Invalid time value."); } /// @@ -38,7 +38,7 @@ public override IValueNode ParseResult(object? resultValue) { if (resultValue is TimeOnly timeOnly) { - return new StringValueNode(timeOnly.ToString(@"hh\:mm\:ss\.fff")); + return new StringValueNode(timeOnly.ToString(@"HH\:mm\:ss\.fff")); } throw new ArgumentException("Invalid TimeOnly value."); @@ -47,9 +47,9 @@ public override IValueNode ParseResult(object? resultValue) /// public override IValueNode ParseValue(object? value) { - if (value is TimeOnly timeSpan) + if (value is TimeOnly timeOnly) { - return new StringValueNode(timeSpan.ToString(@"hh\:mm\:ss\.fff")); + return new StringValueNode(timeOnly.ToString(@"HH\:mm\:ss\.fff")); } throw new ArgumentException("Invalid TimeOnly value."); @@ -58,9 +58,9 @@ public override IValueNode ParseValue(object? value) /// public override object Serialize(object? value) { - if (value is TimeSpan timeSpan) + if (value is TimeOnly timeOnly) { - return timeSpan.ToString(@"hh\:mm\:ss\.fff"); + return timeOnly.ToString(@"HH\:mm\:ss\.fff"); } throw new ArgumentException("Invalid TimeOnly value."); diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs index db0e904990..d761caa12b 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs @@ -71,6 +71,28 @@ public async Task TestStringFiltersEqWithMappings(string dbQuery) SqlTestHelper.PerformTestEqualJsonStrings(expected, actual.ToString()); } + /// + /// Tests gte of TimeOnlyFilterInput where TimeOnly is a custom scalar Hotchocolate Type. + /// + [TestMethod] + public async Task TestTimeOnlyTypeFilter(string dbQuery) + { + string graphQLQueryName = "supportedTypes"; + string gqlQuery = @"{ + supportedTypes( " + QueryBuilder.FILTER_FIELD_NAME + @" : {timeonly_types: {gte: ""10:23:54.999""}}) + { + items { + typeid + timeonly_types + } + } + }"; + + JsonElement actual = await ExecuteGraphQLRequestAsync(gqlQuery, graphQLQueryName, isAuthenticated: false); + string expected = await GetDatabaseResultAsync(dbQuery); + SqlTestHelper.PerformTestEqualJsonStrings(expected, actual.ToString()); + } + /// /// Tests neq of StringFilterInput /// diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs index 7a6b18ec20..86ef16dd9a 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs @@ -58,6 +58,20 @@ ORDER BY [__column1] asc await TestStringFiltersEqWithMappings(msSqlQuery); } + [TestMethod] + public async Task TestTimeOnlyTypeFilter() + { + string msSqlQuery = @" + SELECT [id] AS [typeid], + CONVERT(VARCHAR(12), timeonly_types, 114) AS [timeonly_types] + FROM type_table + WHERE [timeonly_types] >= '10:23:54.999' + ORDER BY [typeid] asc + FOR JSON PATH, INCLUDE_NULL_VALUES"; + + await TestTimeOnlyTypeFilter(msSqlQuery); + } + /// /// Test Nested Filter for One-Many relationship /// From ebd7725ecbbf9eccf8d7fa61340adbf4546e2b3f Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 24 Jul 2023 17:15:15 +0530 Subject: [PATCH 28/50] fixing tests --- .../SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs | 7 +++++++ .../GraphQLFilterTests/PostgreSqlGQLFilterTests.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs index 8262a84497..307273a15a 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs @@ -73,6 +73,13 @@ public void TestNestedFilterWithOr() throw new System.NotImplementedException("Nested Filtering for MySQL is not yet implemented."); } + [Ignore] + [TestMethod] + public void TestTimeOnlyTypeFilter() + { + throw new System.NotImplementedException("Filtering TimeOnly Type for MySQL is not yet implemented."); + } + [TestMethod] public async Task TestStringFiltersEqWithMappings() { diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs index 1d2d2023ee..7b433033b7 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs @@ -168,6 +168,13 @@ public async Task TestStringFiltersEqWithMappings() await TestStringFiltersEqWithMappings(postgresQuery); } + [Ignore] + [TestMethod] + public void TestTimeOnlyTypeFilter() + { + throw new System.NotImplementedException("Filtering TimeOnly Type for PGSQL is not yet implemented."); + } + /// /// Gets the default schema for /// PostgreSql. From 0d0b26366a10c324a4d318c57c0eacc0da1b8254 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 24 Jul 2023 18:04:26 +0530 Subject: [PATCH 29/50] fixing tests --- .../GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs | 4 ++-- .../GraphQLSupportedTypesTestsBase.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs index 1a958ce783..a05c8d9829 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs @@ -43,13 +43,13 @@ public static async Task SetupAsync(TestContext context) DisplayName = "Test after token for decimal values.")] [DataRow("boolean_types", "false", "true", 2, 4, DisplayName = "Test after token for boolean values.")] - [DataRow("date_types", "\"0001-01-01\"", + [DataRow("date_types", "\"1753-01-01\"", "\"9999-12-31\"", 3, 4, DisplayName = "Test after token for date values.")] [DataRow("datetime_types", "\"1753-01-01T00:00:00.000\"", "\"9999-12-31T23:59:59\"", 3, 4, DisplayName = "Test after token for datetime values.")] - [DataRow("datetime2_types", "\"0001-01-01 00:00:00.0000000\"", + [DataRow("datetime2_types", "\"1753-01-01 00:00:00.0000000\"", "\"9999-12-31T23:59:59.9999999\"", 3, 4, DisplayName = "Test after token for datetime2 values.")] [DataRow("datetimeoffset_types", "\"0001-01-01 00:00:00.0000000+0:00\"", diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index 31a9250141..c33da1926b 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -222,7 +222,7 @@ public async Task InsertIntoTypeColumnWithArgument(string type, object value) [DataRow(DATETIME_TYPE, "\"1999-01-08 09:20:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08\"")] [DataRow(DATETIME_TYPE, "null")] - [DataRow(TIMEONLY_TYPE, "23:59:59.9999999")] + [DataRow(TIMEONLY_TYPE, "\"23:59:59.9999999\"")] [DataRow(TIMEONLY_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "\"U3RyaW5neQ==\"")] [DataRow(BYTEARRAY_TYPE, "\"V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=\"")] From 33edfaa667baa06b29f135c99aa150bbd34a9ef4 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 25 Jul 2023 22:44:07 +0530 Subject: [PATCH 30/50] using Hotchocolate.Types.NodaTime --- src/Core/Azure.DataApiBuilder.Core.csproj | 1 + .../BaseSqlQueryStructure.cs | 2 +- src/Core/Services/ResolverMiddleware.cs | 5 +- src/Directory.Packages.props | 1 + ...taApiBuilder.Service.GraphQLBuilder.csproj | 1 + .../GraphQLTypes/DefaultValueType.cs | 3 +- .../GraphQLTypes/SupportedTypes.cs | 5 +- .../GraphQLTypes/TimeOnlytype.cs | 68 ------------------- src/Service.GraphQLBuilder/GraphQLUtils.cs | 10 ++- .../Queries/StandardQueryInputs.cs | 32 ++++----- .../Sql/SchemaConverter.cs | 8 +-- src/Service.Tests/DatabaseSchema-MsSql.sql | 4 +- .../Sql/SchemaConverterTests.cs | 2 +- .../Sql/StoredProcedureBuilderTests.cs | 2 +- .../CLRtoJsonValueTypeUnitTests.cs | 1 + .../GraphQLFilterTestBase.cs | 8 +-- .../GraphQLFilterTests/MsSqlGQLFilterTests.cs | 8 +-- .../GraphQLFilterTests/MySqlGQLFilterTests.cs | 4 +- .../PostgreSqlGQLFilterTests.cs | 4 +- .../GraphQLSupportedTypesTestsBase.cs | 46 +++++++++---- .../MySqlGQLSupportedTypesTests.cs | 3 +- .../PostgreSqlGQLSupportedTypesTests.cs | 2 +- .../Azure.DataApiBuilder.Service.csproj | 1 + 23 files changed, 88 insertions(+), 133 deletions(-) delete mode 100644 src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs diff --git a/src/Core/Azure.DataApiBuilder.Core.csproj b/src/Core/Azure.DataApiBuilder.Core.csproj index 3850c5f587..a4ac48aeb5 100644 --- a/src/Core/Azure.DataApiBuilder.Core.csproj +++ b/src/Core/Azure.DataApiBuilder.Core.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs b/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs index 637c84eb4f..2cf9ca3ff8 100644 --- a/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs +++ b/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs @@ -364,9 +364,9 @@ protected static object ParseParamAsSystemType(string param, Type systemType) "DateTime" => DateTime.Parse(param, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), "DateTimeOffset" => DateTimeOffset.Parse(param), "Date" => DateOnly.Parse(param), + "Guid" => Guid.Parse(param), "TimeOnly" => TimeOnly.Parse(param), "TimeSpan" => TimeOnly.Parse(param), - "Guid" => Guid.Parse(param), _ => throw new NotSupportedException($"{systemType.Name} is not supported") }; } diff --git a/src/Core/Services/ResolverMiddleware.cs b/src/Core/Services/ResolverMiddleware.cs index 2b115459c4..d3941e7805 100644 --- a/src/Core/Services/ResolverMiddleware.cs +++ b/src/Core/Services/ResolverMiddleware.cs @@ -7,12 +7,13 @@ using Azure.DataApiBuilder.Core.Models; using Azure.DataApiBuilder.Core.Resolvers; using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars; -using Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes; using HotChocolate.Execution; using HotChocolate.Language; using HotChocolate.Resolvers; +using HotChocolate.Types.NodaTime; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; +using NodaTime.Text; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedTypes; namespace Azure.DataApiBuilder.Core.Services @@ -200,7 +201,7 @@ private static object PreParseLeaf(IMiddlewareContext context, string leafJson) SingleType => Single.Parse(leafJson), DateTimeType => DateTimeOffset.Parse(leafJson, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), ByteArrayType => Convert.FromBase64String(leafJson), - TimeOnlyType => TimeOnly.Parse(leafJson, DateTimeFormatInfo.InvariantInfo), + LocalTimeType => LocalTimePattern.ExtendedIso.Parse(leafJson).Value, _ => leafJson }; } diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 1e1c985184..ec66e68506 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -9,6 +9,7 @@ + diff --git a/src/Service.GraphQLBuilder/Azure.DataApiBuilder.Service.GraphQLBuilder.csproj b/src/Service.GraphQLBuilder/Azure.DataApiBuilder.Service.GraphQLBuilder.csproj index 71530f1eba..80fc4b94eb 100644 --- a/src/Service.GraphQLBuilder/Azure.DataApiBuilder.Service.GraphQLBuilder.csproj +++ b/src/Service.GraphQLBuilder/Azure.DataApiBuilder.Service.GraphQLBuilder.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs b/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs index f17628ef7d..be27c13555 100644 --- a/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs +++ b/src/Service.GraphQLBuilder/GraphQLTypes/DefaultValueType.cs @@ -3,6 +3,7 @@ using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars; using HotChocolate.Types; +using HotChocolate.Types.NodaTime; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedTypes; namespace Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes @@ -24,7 +25,7 @@ protected override void Configure(IInputObjectTypeDescriptor descriptor) descriptor.Field(DECIMAL_TYPE).Type(); descriptor.Field(DATETIME_TYPE).Type(); descriptor.Field(BYTEARRAY_TYPE).Type(); - descriptor.Field(TIMEONLY_TYPE).Type(); + descriptor.Field(LOCALTIME_TYPE).Type(); } } } diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs b/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs index cd0160a045..4470f8e2d1 100644 --- a/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs +++ b/src/Service.GraphQLBuilder/GraphQLTypes/SupportedTypes.cs @@ -18,11 +18,10 @@ public static class SupportedTypes public const string STRING_TYPE = "String"; public const string BOOLEAN_TYPE = "Boolean"; public const string DATETIME_TYPE = "DateTime"; - // The DATETIME_NONUTC_TYPE constant is only used in testing - // since PostgreSQL doesn't support datetime values with a non-UTC time zone. public const string DATETIMEOFFSET_TYPE = "DateTimeOffset"; public const string BYTEARRAY_TYPE = "ByteArray"; - public const string TIMEONLY_TYPE = "TimeOnly"; public const string GUID_TYPE = "Guid"; + public const string LOCALTIME_TYPE = "LocalTime"; + public const string TIME_TYPE = "Time"; } } diff --git a/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs b/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs deleted file mode 100644 index e20b15be55..0000000000 --- a/src/Service.GraphQLBuilder/GraphQLTypes/TimeOnlytype.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using HotChocolate.Language; -using HotChocolate.Types; - -namespace Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes; -public class TimeOnlyType : ScalarType -{ - /// - /// Initializes a new instance of the class. - /// - public TimeOnlyType() : base("TimeOnly") - { } - - /// - public override bool IsInstanceOfType(IValueNode valueSyntax) - { - return valueSyntax is StringValueNode; - } - - /// - public override object ParseLiteral(IValueNode literal) - { - if (literal is StringValueNode stringValueNode) - { - if (TimeSpan.TryParse(stringValueNode.Value, out TimeSpan timeSpan)) - { - return new TimeOnly(timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds, timeSpan.Milliseconds); - } - } - - throw new ArgumentException("Invalid time value."); - } - - /// - public override IValueNode ParseResult(object? resultValue) - { - if (resultValue is TimeOnly timeOnly) - { - return new StringValueNode(timeOnly.ToString(@"HH\:mm\:ss\.fff")); - } - - throw new ArgumentException("Invalid TimeOnly value."); - } - - /// - public override IValueNode ParseValue(object? value) - { - if (value is TimeOnly timeOnly) - { - return new StringValueNode(timeOnly.ToString(@"HH\:mm\:ss\.fff")); - } - - throw new ArgumentException("Invalid TimeOnly value."); - } - - /// - public override object Serialize(object? value) - { - if (value is TimeOnly timeOnly) - { - return timeOnly.ToString(@"HH\:mm\:ss\.fff"); - } - - throw new ArgumentException("Invalid TimeOnly value."); - } -} diff --git a/src/Service.GraphQLBuilder/GraphQLUtils.cs b/src/Service.GraphQLBuilder/GraphQLUtils.cs index 0d9f967e62..b537e46ba6 100644 --- a/src/Service.GraphQLBuilder/GraphQLUtils.cs +++ b/src/Service.GraphQLBuilder/GraphQLUtils.cs @@ -2,17 +2,16 @@ // 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.GraphQLTypes; using Azure.DataApiBuilder.Service.GraphQLBuilder.Sql; using HotChocolate.Language; using HotChocolate.Types; +using HotChocolate.Types.NodaTime; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedTypes; namespace Azure.DataApiBuilder.Service.GraphQLBuilder @@ -55,7 +54,7 @@ public static bool IsBuiltInType(ITypeNode typeNode) BOOLEAN_TYPE, DATETIME_TYPE, BYTEARRAY_TYPE, - TIMEONLY_TYPE + LOCALTIME_TYPE }; string name = typeNode.NamedType().Name.Value; return inBuiltTypes.Contains(name); @@ -242,10 +241,9 @@ public static Tuple ConvertValueToGraphQLType(string default 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))), - TIMEONLY_TYPE => new(TIMEONLY_TYPE, new TimeOnlyType().ParseResult(TimeOnly.Parse(defaultValueFromConfig))), + DATETIME_TYPE => new(DATETIME_TYPE, new DateTimeType().ParseResult(DateTime.Parse(defaultValueFromConfig))), BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))), + LOCALTIME_TYPE => new(LOCALTIME_TYPE, new LocalTimeType().ParseResult(TimeOnly.Parse(defaultValueFromConfig))), _ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.") }; diff --git a/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs b/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs index 5411a782a1..365aca7f74 100644 --- a/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs +++ b/src/Service.GraphQLBuilder/Queries/StandardQueryInputs.cs @@ -2,9 +2,9 @@ // Licensed under the MIT License. using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars; -using Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes; using HotChocolate.Language; using HotChocolate.Types; +using HotChocolate.Types.NodaTime; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedTypes; namespace Azure.DataApiBuilder.Service.GraphQLBuilder.Queries @@ -191,31 +191,31 @@ public static InputObjectTypeDefinitionNode DateTimeInputType() => } ); - public static InputObjectTypeDefinitionNode TimeOnlyInputType() => + public static InputObjectTypeDefinitionNode ByteArrayInputType() => new( location: null, - new NameNode("TimeOnlyFilterInput"), - new StringValueNode("Input type for adding TimeOnly filters"), + new NameNode("ByteArrayFilterInput"), + new StringValueNode("Input type for adding ByteArray filters"), new List(), new List { - new InputValueDefinitionNode(null, new NameNode("eq"), new StringValueNode("Equals"), new TimeOnlyType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("gt"), new StringValueNode("Greater Than"), new TimeOnlyType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("gte"), new StringValueNode("Greater Than or Equal To"), new TimeOnlyType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("lt"), new StringValueNode("Less Than"), new TimeOnlyType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("lte"), new StringValueNode("Less Than or Equal To"), new TimeOnlyType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("neq"), new StringValueNode("Not Equals"), new TimeOnlyType().ToTypeNode(), null, new List()), - new InputValueDefinitionNode(null, new NameNode("isNull"), new StringValueNode("is null test"), new BooleanType().ToTypeNode(), null, new List()) + new InputValueDefinitionNode(null, new NameNode("isNull"), new StringValueNode("Not null test"), new BooleanType().ToTypeNode(), null, new List()) } ); - public static InputObjectTypeDefinitionNode ByteArrayInputType() => + public static InputObjectTypeDefinitionNode LocalTimeInputType() => new( location: null, - new NameNode("ByteArrayFilterInput"), - new StringValueNode("Input type for adding ByteArray filters"), + new NameNode("LocalTimeFilterInput"), + new StringValueNode("Input type for adding LocalTime filters"), new List(), new List { - new InputValueDefinitionNode(null, new NameNode("isNull"), new StringValueNode("Not null test"), new BooleanType().ToTypeNode(), null, new List()) + new InputValueDefinitionNode(null, new NameNode("eq"), new StringValueNode("Equals"), new LocalTimeType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("gt"), new StringValueNode("Greater Than"), new LocalTimeType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("gte"), new StringValueNode("Greater Than or Equal To"), new LocalTimeType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("lt"), new StringValueNode("Less Than"), new LocalTimeType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("lte"), new StringValueNode("Less Than or Equal To"), new LocalTimeType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("neq"), new StringValueNode("Not Equals"), new LocalTimeType().ToTypeNode(), null, new List()), + new InputValueDefinitionNode(null, new NameNode("isNull"), new StringValueNode("is null test"), new BooleanType().ToTypeNode(), null, new List()) } ); @@ -233,7 +233,7 @@ public static InputObjectTypeDefinitionNode ByteArrayInputType() => { STRING_TYPE, StringInputType() }, { DATETIME_TYPE, DateTimeInputType() }, { BYTEARRAY_TYPE, ByteArrayInputType() }, - { TIMEONLY_TYPE, TimeOnlyInputType() }, + { LOCALTIME_TYPE, LocalTimeInputType() }, }; /// diff --git a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs index 5e963a6e34..d51d397a71 100644 --- a/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs +++ b/src/Service.GraphQLBuilder/Sql/SchemaConverter.cs @@ -9,10 +9,10 @@ using Azure.DataApiBuilder.Service.Exceptions; using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars; using Azure.DataApiBuilder.Service.GraphQLBuilder.Directives; -using Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes; using Azure.DataApiBuilder.Service.GraphQLBuilder.Queries; using HotChocolate.Language; using HotChocolate.Types; +using HotChocolate.Types.NodaTime; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLNaming; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLStoredProcedureBuilder; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedTypes; @@ -232,8 +232,8 @@ public static string GetGraphQLTypeFromSystemType(Type type) "DateTime" => DATETIME_TYPE, "DateTimeOffset" => DATETIME_TYPE, "Byte[]" => BYTEARRAY_TYPE, - "TimeOnly" => TIMEONLY_TYPE, - "TimeSpan" => TIMEONLY_TYPE, + "TimeOnly" => LOCALTIME_TYPE, + "TimeSpan" => LOCALTIME_TYPE, _ => throw new DataApiBuilderException( message: $"Column type {type} not handled by case. Please add a case resolving {type} to the appropriate GraphQL type", statusCode: HttpStatusCode.InternalServerError, @@ -266,8 +266,8 @@ public static IValueNode CreateValueNodeFromDbObjectMetadata(object metadataValu decimal value => new ObjectValueNode(new ObjectFieldNode(DECIMAL_TYPE, new FloatValueNode(value))), DateTimeOffset value => new ObjectValueNode(new ObjectFieldNode(DATETIME_TYPE, new DateTimeType().ParseValue(value))), DateTime value => new ObjectValueNode(new ObjectFieldNode(DATETIME_TYPE, new DateTimeType().ParseResult(value))), - TimeOnly value => new ObjectValueNode(new ObjectFieldNode(TIMEONLY_TYPE, new TimeOnlyType().ParseResult(value))), byte[] value => new ObjectValueNode(new ObjectFieldNode(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(value))), + TimeOnly value => new ObjectValueNode(new ObjectFieldNode(LOCALTIME_TYPE, new LocalTimeType().ParseResult(value))), _ => throw new DataApiBuilderException( message: $"The type {metadataValue.GetType()} is not supported as a GraphQL default value", statusCode: HttpStatusCode.InternalServerError, diff --git a/src/Service.Tests/DatabaseSchema-MsSql.sql b/src/Service.Tests/DatabaseSchema-MsSql.sql index 22129ceeb0..4768723a12 100644 --- a/src/Service.Tests/DatabaseSchema-MsSql.sql +++ b/src/Service.Tests/DatabaseSchema-MsSql.sql @@ -162,7 +162,7 @@ CREATE TABLE type_table( datetime2_types datetime2, datetimeoffset_types datetimeoffset, smalldatetime_types smalldatetime, - timeonly_types time, + time_types time, bytearray_types varbinary(max), guid_types uniqueidentifier DEFAULT newid() ); @@ -399,7 +399,7 @@ byte_types, short_types, int_types, long_types, string_types, single_types, float_types, decimal_types, boolean_types, -date_types, datetime_types, datetime2_types, datetimeoffset_types, smalldatetime_types, timeonly_types, +date_types, datetime_types, datetime2_types, datetimeoffset_types, smalldatetime_types, time_types, bytearray_types) VALUES (1, 1, 1, 1, 1, '', 0.33, 0.33, 0.333333, 1, diff --git a/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs index c310bdd749..d6da633379 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs @@ -230,7 +230,7 @@ public void MultipleColumnsAllMapped() [DataRow(typeof(DateTimeOffset), DATETIME_TYPE)] [DataRow(typeof(byte[]), BYTEARRAY_TYPE)] [DataRow(typeof(Guid), STRING_TYPE)] - [DataRow(typeof(TimeOnly), TIMEONLY_TYPE)] + [DataRow(typeof(TimeOnly), LOCALTIME_TYPE)] public void SystemTypeMapsToCorrectGraphQLType(Type systemType, string graphQLType) { SourceDefinition table = new(); diff --git a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs index 7ed46419e7..be33d35d58 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs @@ -54,7 +54,7 @@ public class StoredProcedureBuilderTests [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12:00:00 AM", false, DisplayName = "DateTime")] [DataRow(typeof(DateTime), DATETIME_TYPE, "12/31/2030 12000 AM", true, DisplayName = "DateTime")] [DataRow(typeof(DateTimeOffset), DATETIME_TYPE, "11/19/2012 10:57:11 AM -08:00", false, DisplayName = "DateTimeOffset")] - [DataRow(typeof(TimeOnly), TIMEONLY_TYPE, "10:57:11.0000", false, DisplayName = "TimeOnly")] + [DataRow(typeof(TimeOnly), LOCALTIME_TYPE, "10:57:11.0000", false, DisplayName = "LocalTime")] [DataRow(typeof(byte[]), BYTEARRAY_TYPE, "AgQGCAoMDhASFA==", false, DisplayName = "Byte[]")] public void StoredProcedure_ParameterValueTypeResolution( Type systemType, diff --git a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs index fc68b4bb14..3dd726f952 100644 --- a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs +++ b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs @@ -100,6 +100,7 @@ private static IEnumerable GetTestData_SupportedSystemTypesMapToJsonVa [DataRow(typeof(char?))] [DataRow(typeof(Guid?))] [DataRow(typeof(TimeOnly?))] + [DataRow(typeof(TimeSpan?))] [DataRow(typeof(DateTime?))] [DataRow(typeof(DateTimeOffset?))] [DataTestMethod] diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs index d761caa12b..99512cc50f 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs @@ -72,18 +72,18 @@ public async Task TestStringFiltersEqWithMappings(string dbQuery) } /// - /// Tests gte of TimeOnlyFilterInput where TimeOnly is a custom scalar Hotchocolate Type. + /// Tests gte of LocalTimeFilterInput where LocalTime is a NodatTime Hotchocolate Type. /// [TestMethod] - public async Task TestTimeOnlyTypeFilter(string dbQuery) + public async Task TestLocalTimeTypeFilter(string dbQuery) { string graphQLQueryName = "supportedTypes"; string gqlQuery = @"{ - supportedTypes( " + QueryBuilder.FILTER_FIELD_NAME + @" : {timeonly_types: {gte: ""10:23:54.999""}}) + supportedTypes( " + QueryBuilder.FILTER_FIELD_NAME + @" : {time_types: {gte: ""10:23:54.999""}}) { items { typeid - timeonly_types + time_types } } }"; diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs index 86ef16dd9a..d1d0bdfe65 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs @@ -59,17 +59,17 @@ ORDER BY [__column1] asc } [TestMethod] - public async Task TestTimeOnlyTypeFilter() + public async Task TestLocalTimeTypeFilter() { string msSqlQuery = @" SELECT [id] AS [typeid], - CONVERT(VARCHAR(12), timeonly_types, 114) AS [timeonly_types] + [time_types] FROM type_table - WHERE [timeonly_types] >= '10:23:54.999' + WHERE [time_types] >= '10:23:54.999' ORDER BY [typeid] asc FOR JSON PATH, INCLUDE_NULL_VALUES"; - await TestTimeOnlyTypeFilter(msSqlQuery); + await TestLocalTimeTypeFilter(msSqlQuery); } /// diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs index 307273a15a..9c7887f286 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs @@ -75,9 +75,9 @@ public void TestNestedFilterWithOr() [Ignore] [TestMethod] - public void TestTimeOnlyTypeFilter() + public void TestLocalTimeTypeFilter() { - throw new System.NotImplementedException("Filtering TimeOnly Type for MySQL is not yet implemented."); + throw new System.NotImplementedException("Filtering LocalTime Type for MySQL is not yet implemented."); } [TestMethod] diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs index 7b433033b7..46bf44720e 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs @@ -170,9 +170,9 @@ public async Task TestStringFiltersEqWithMappings() [Ignore] [TestMethod] - public void TestTimeOnlyTypeFilter() + public void TestLocalTimeTypeFilter() { - throw new System.NotImplementedException("Filtering TimeOnly Type for PGSQL is not yet implemented."); + throw new System.NotImplementedException("Filtering LocalTime Type for PGSQL is not yet implemented."); } /// diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index c33da1926b..817cda886b 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedTypes; @@ -60,11 +61,11 @@ public abstract class GraphQLSupportedTypesTestBase : SqlTestBase [DataRow(DATETIME_TYPE, 2)] [DataRow(DATETIME_TYPE, 3)] [DataRow(DATETIME_TYPE, 4)] - [DataRow(TIMEONLY_TYPE, 1)] - [DataRow(TIMEONLY_TYPE, 2)] - [DataRow(TIMEONLY_TYPE, 3)] - [DataRow(TIMEONLY_TYPE, 4)] - [DataRow(TIMEONLY_TYPE, 5)] + [DataRow(TIME_TYPE, 1)] + [DataRow(TIME_TYPE, 2)] + [DataRow(TIME_TYPE, 3)] + [DataRow(TIME_TYPE, 4)] + [DataRow(TIME_TYPE, 5)] [DataRow(BYTEARRAY_TYPE, 1)] [DataRow(BYTEARRAY_TYPE, 2)] [DataRow(BYTEARRAY_TYPE, 3)] @@ -127,9 +128,10 @@ public async Task QueryTypeColumn(string type, int id) [DataRow(DATETIME_TYPE, "\"1999-01-08 09:20:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08\"")] [DataRow(DATETIME_TYPE, "null")] - [DataRow(TIMEONLY_TYPE, "\"23:59:59.9999999\"")] - [DataRow(TIMEONLY_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "\"U3RyaW5neQ==\"")] + [DataRow(TIME_TYPE, "\"23:59:59.9999999\"")] + [DataRow(TIME_TYPE, "\"23:59:59\"")] + [DataRow(TIME_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "\"V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=\"")] [DataRow(BYTEARRAY_TYPE, "null")] public async Task InsertIntoTypeColumn(string type, string value) @@ -153,6 +155,26 @@ public async Task InsertIntoTypeColumn(string type, string value) await ResetDbStateAsync(); } + [DataTestMethod] + [DataRow(TIME_TYPE, "\"32:59:59.9999999\"")] + [DataRow(TIME_TYPE, "\"22:67:59.9999999\"")] + public async Task InsertInvalidIntoTypeColumn(string type, string value) + { + if (!IsSupportedType(type)) + { + Assert.Inconclusive("Type not supported"); + } + + string field = $"{type.ToLowerInvariant()}_types"; + string graphQLQueryName = "createSupportedType"; + string gqlQuery = "mutation{ createSupportedType (item: {" + field + ": " + value + " }){ " + field + " } }"; + + JsonElement response = await ExecuteGraphQLRequestAsync(gqlQuery, graphQLQueryName, isAuthenticated: true); + string responseMessage = Regex.Unescape(JsonSerializer.Serialize(response)); + Assert.IsTrue(responseMessage.Contains($"{value} cannot be resolved as column \"{field}\" with type \"TimeSpan\".")); + await ResetDbStateAsync(); + } + [DataTestMethod] [DataRow(BYTE_TYPE, 255)] [DataRow(SHORT_TYPE, 30000)] @@ -164,7 +186,6 @@ public async Task InsertIntoTypeColumn(string type, string value) [DataRow(BOOLEAN_TYPE, true)] [DataRow(DATETIMEOFFSET_TYPE, "1999-01-08 10:23:54+8:00")] [DataRow(DATETIME_TYPE, "1999-01-08 10:23:54")] - [DataRow(TIMEONLY_TYPE, "23:59:59.9999999")] [DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")] public async Task InsertIntoTypeColumnWithArgument(string type, object value) { @@ -222,8 +243,8 @@ public async Task InsertIntoTypeColumnWithArgument(string type, object value) [DataRow(DATETIME_TYPE, "\"1999-01-08 09:20:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08\"")] [DataRow(DATETIME_TYPE, "null")] - [DataRow(TIMEONLY_TYPE, "\"23:59:59.9999999\"")] - [DataRow(TIMEONLY_TYPE, "null")] + [DataRow(TIME_TYPE, "\"23:59:59.9999999\"")] + [DataRow(TIME_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "\"U3RyaW5neQ==\"")] [DataRow(BYTEARRAY_TYPE, "\"V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=\"")] [DataRow(BYTEARRAY_TYPE, "null")] @@ -261,7 +282,6 @@ public async Task UpdateTypeColumn(string type, string value) [DataRow(BOOLEAN_TYPE, true)] [DataRow(DATETIME_TYPE, "1999-01-08 10:23:54")] [DataRow(DATETIMEOFFSET_TYPE, "1999-01-08 10:23:54+8:00")] - [DataRow(TIMEONLY_TYPE, "23:59:59.9999999")] [DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")] [DataRow(GUID_TYPE, "3a1483a5-9ac2-4998-bcf3-78a28078c6ac")] [DataRow(GUID_TYPE, null)] @@ -306,7 +326,7 @@ private static void PerformTestEqualsForExtendedTypes(string type, string expect { CompareDateTimeOffsetResults(actual.ToString(), expected); } - else if (type == TIMEONLY_TYPE) + else if (type == TIME_TYPE) { CompareTimeResults(actual.ToString(), expected); } @@ -414,7 +434,7 @@ private static void CompareDateTimeOffsetResults(string actual, string expected) /// private static void CompareTimeResults(string actual, string expected) { - string fieldName = "timeonly_types"; + string fieldName = "time_types"; using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs index b673e6350e..135a6c41d6 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs @@ -42,8 +42,7 @@ protected override bool IsSupportedType(string type) return type switch { GUID_TYPE => false, - DATETIMEOFFSET_TYPE => false, - TIMEONLY_TYPE => false, + TIME_TYPE => false, _ => true }; } diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs index 8bfedb83d8..34e6a5dbd1 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/PostgreSqlGQLSupportedTypesTests.cs @@ -42,7 +42,7 @@ protected override bool IsSupportedType(string type) { BYTE_TYPE => false, DATETIMEOFFSET_TYPE => false, - TIMEONLY_TYPE => false, + TIME_TYPE => false, _ => true }; } diff --git a/src/Service/Azure.DataApiBuilder.Service.csproj b/src/Service/Azure.DataApiBuilder.Service.csproj index 8338c6c059..156dcc7436 100644 --- a/src/Service/Azure.DataApiBuilder.Service.csproj +++ b/src/Service/Azure.DataApiBuilder.Service.csproj @@ -51,6 +51,7 @@ + From 9883d8785189df52346d0e95fd6b5c57a6663915 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Wed, 26 Jul 2023 11:08:44 +0530 Subject: [PATCH 31/50] fix formatting --- .../MsSqlGraphQLPaginationTests.cs | 4 ++-- .../GraphQLSupportedTypesTestsBase.cs | 12 ++++++------ .../MySqlGQLSupportedTypesTests.cs | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs index a55ee7fad6..0a5ce1938c 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs @@ -44,13 +44,13 @@ public static async Task SetupAsync(TestContext context) [DataRow("boolean_types", "false", "true", 2, 4, DisplayName = "Test after token for boolean values.")] [DataRow("date_types", "\"1753-01-01\"", - "\"9999-12-31\"", 3, 4, + "\"9998-12-31\"", 3, 4, DisplayName = "Test after token for date values.")] [DataRow("datetime_types", "\"1753-01-01T00:00:00.000\"", "\"9998-12-31T23:59:59\"", 3, 4, DisplayName = "Test after token for datetime values.")] [DataRow("datetime2_types", "\"1753-01-01 00:00:00.0000000\"", - "\"9999-12-31T23:59:59.9999999\"", 3, 4, + "\"9998-12-31T23:59:59.9999999\"", 3, 4, DisplayName = "Test after token for datetime2 values.")] [DataRow("datetimeoffset_types", "\"0001-01-01 00:00:00.0000000+0:00\"", "\"9998-12-31T23:59:59.9999999+00:00\"", 3, 4, diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index 5dfa0089c6..a6c5d4fa5c 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -173,10 +173,10 @@ public async Task QueryTypeColumnFilterAndOrderBy(string type, string filterOper [DataTestMethod] [DataRow(DATETIME_TYPE, "gt", "\'1999-01-08\'", "\"1999-01-08\"", " > ")] [DataRow(DATETIME_TYPE, "gte", "\'1999-01-08\'", "\"1999-01-08\"", " >= ")] - [DataRow(DATETIME_TYPE, "lt", "\'0001-01-01\'", "\"0001-01-01\"", " < ")] - [DataRow(DATETIME_TYPE, "lte", "\'0001-01-01\'", "\"0001-01-01\"", " <= ")] - [DataRow(DATETIME_TYPE, "neq", "\'0001-01-01\'", "\"0001-01-01\"", "!=")] - [DataRow(DATETIME_TYPE, "eq", "\'0001-01-01\'", "\"0001-01-01T01:01:01\"", "=")] + [DataRow(DATETIME_TYPE, "lt", "\'1999-01-08\'", "\"1999-01-08\"", " < ")] + [DataRow(DATETIME_TYPE, "lte", "\'1999-01-08\'", "\"1999-01-08\"", " <= ")] + [DataRow(DATETIME_TYPE, "neq", "\'1999-01-08\'", "\"1999-01-08\"", "!=")] + [DataRow(DATETIME_TYPE, "eq", "\'1999-01-08\'", "\"1999-01-08T01:01:01\"", "=")] [DataRow(DATETIME_TYPE, "gt", "\'1999-01-08 10:23:00\'", "\"1999-01-08 10:23:00\"", " > ")] [DataRow(DATETIME_TYPE, "gte", "\'1999-01-08 10:23:00\'", "\"1999-01-08 10:23:00\"", " >= ")] [DataRow(DATETIME_TYPE, "lt", "\'9998-12-31 23:59:59\'", "\"9998-12-31 23:59:59\"", " < ")] @@ -591,8 +591,8 @@ private static void ValidateArrayResults(JsonDocument actualJsonDoc, JsonDocumen if (fieldName.StartsWith(DATETIME_TYPE.ToLower())) { // MySql returns a format that will not directly parse into DateTime type so we use string here for parsing - DateTime actualDateTime = DateTime.Parse(actualValue.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.None); - DateTime expectedDateTime = DateTime.Parse(expectedValue.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.None); + DateTime actualDateTime = DateTime.Parse(actualValue.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + DateTime expectedDateTime = DateTime.Parse(expectedValue.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); Assert.AreEqual(expectedDateTime, actualDateTime); } else if (fieldName.StartsWith(SINGLE_TYPE.ToLower())) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs index d59ecafb50..a0a6863fa6 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/MySqlGQLSupportedTypesTests.cs @@ -57,6 +57,7 @@ protected override bool IsSupportedType(string type) return type switch { GUID_TYPE => false, + DATETIMEOFFSET_TYPE => false, TIME_TYPE => false, _ => true }; From a3e968ef2ff723effe2b00cbd54ba82740eab063 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Wed, 26 Jul 2023 11:39:24 +0530 Subject: [PATCH 32/50] fixing tests --- src/Service.GraphQLBuilder/GraphQLUtils.cs | 7 +++++-- .../SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Service.GraphQLBuilder/GraphQLUtils.cs b/src/Service.GraphQLBuilder/GraphQLUtils.cs index b537e46ba6..972ec5f5b4 100644 --- a/src/Service.GraphQLBuilder/GraphQLUtils.cs +++ b/src/Service.GraphQLBuilder/GraphQLUtils.cs @@ -2,6 +2,7 @@ // 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; @@ -12,6 +13,7 @@ using HotChocolate.Language; using HotChocolate.Types; using HotChocolate.Types.NodaTime; +using NodaTime.Text; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedTypes; namespace Azure.DataApiBuilder.Service.GraphQLBuilder @@ -241,9 +243,10 @@ public static Tuple ConvertValueToGraphQLType(string default 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))), + 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(TimeOnly.Parse(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.") }; diff --git a/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs index b8d1bd6d4b..ec15851c6e 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Insert/MsSqlInsertApiTests.cs @@ -32,7 +32,7 @@ public class MsSqlInsertApiTests : InsertApiTestBase "InsertOneInSupportedTypes", $"SELECT [id] as [typeid], [byte_types], [short_types], [int_types], [long_types],string_types, [single_types], [float_types], " + $"[decimal_types], [boolean_types], [date_types], [datetime_types], [datetime2_types], [datetimeoffset_types], [smalldatetime_types], " + - $"[timeonly_types], [bytearray_types], LOWER([guid_types]) as [guid_types] FROM { _integrationTypeTable } " + + $"[time_types], [bytearray_types], LOWER([guid_types]) as [guid_types] FROM { _integrationTypeTable } " + $"WHERE [id] = { STARTING_ID_FOR_TEST_INSERTS } AND [bytearray_types] is NULL " + $"FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER" }, From de932001873dcc9e5cc1cc9f6208ca6b8895a683 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Wed, 26 Jul 2023 14:44:36 +0530 Subject: [PATCH 33/50] adding tests --- .../GraphQLFilterTestBase.cs | 22 --------------- .../GraphQLFilterTests/MsSqlGQLFilterTests.cs | 14 ---------- .../GraphQLFilterTests/MySqlGQLFilterTests.cs | 7 ----- .../PostgreSqlGQLFilterTests.cs | 7 ----- .../GraphQLSupportedTypesTestsBase.cs | 27 +++++++++++++++++++ 5 files changed, 27 insertions(+), 50 deletions(-) diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs index 99512cc50f..db0e904990 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/GraphQLFilterTestBase.cs @@ -71,28 +71,6 @@ public async Task TestStringFiltersEqWithMappings(string dbQuery) SqlTestHelper.PerformTestEqualJsonStrings(expected, actual.ToString()); } - /// - /// Tests gte of LocalTimeFilterInput where LocalTime is a NodatTime Hotchocolate Type. - /// - [TestMethod] - public async Task TestLocalTimeTypeFilter(string dbQuery) - { - string graphQLQueryName = "supportedTypes"; - string gqlQuery = @"{ - supportedTypes( " + QueryBuilder.FILTER_FIELD_NAME + @" : {time_types: {gte: ""10:23:54.999""}}) - { - items { - typeid - time_types - } - } - }"; - - JsonElement actual = await ExecuteGraphQLRequestAsync(gqlQuery, graphQLQueryName, isAuthenticated: false); - string expected = await GetDatabaseResultAsync(dbQuery); - SqlTestHelper.PerformTestEqualJsonStrings(expected, actual.ToString()); - } - /// /// Tests neq of StringFilterInput /// diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs index d1d0bdfe65..7a6b18ec20 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/MsSqlGQLFilterTests.cs @@ -58,20 +58,6 @@ ORDER BY [__column1] asc await TestStringFiltersEqWithMappings(msSqlQuery); } - [TestMethod] - public async Task TestLocalTimeTypeFilter() - { - string msSqlQuery = @" - SELECT [id] AS [typeid], - [time_types] - FROM type_table - WHERE [time_types] >= '10:23:54.999' - ORDER BY [typeid] asc - FOR JSON PATH, INCLUDE_NULL_VALUES"; - - await TestLocalTimeTypeFilter(msSqlQuery); - } - /// /// Test Nested Filter for One-Many relationship /// diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs index 9c7887f286..8262a84497 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/MySqlGQLFilterTests.cs @@ -73,13 +73,6 @@ public void TestNestedFilterWithOr() throw new System.NotImplementedException("Nested Filtering for MySQL is not yet implemented."); } - [Ignore] - [TestMethod] - public void TestLocalTimeTypeFilter() - { - throw new System.NotImplementedException("Filtering LocalTime Type for MySQL is not yet implemented."); - } - [TestMethod] public async Task TestStringFiltersEqWithMappings() { diff --git a/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs b/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs index 46bf44720e..1d2d2023ee 100644 --- a/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLFilterTests/PostgreSqlGQLFilterTests.cs @@ -168,13 +168,6 @@ public async Task TestStringFiltersEqWithMappings() await TestStringFiltersEqWithMappings(postgresQuery); } - [Ignore] - [TestMethod] - public void TestLocalTimeTypeFilter() - { - throw new System.NotImplementedException("Filtering LocalTime Type for PGSQL is not yet implemented."); - } - /// /// Gets the default schema for /// PostgreSql. diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index a6c5d4fa5c..c3879627c8 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -202,6 +202,21 @@ public async Task QueryTypeColumnFilterAndOrderByDateTime(string type, string fi await QueryTypeColumnFilterAndOrderBy(type, filterOperator, sqlValue, gqlValue, queryOperator); } + /// + /// Test case for LocalTime filters. + /// + [DataTestMethod] + [DataRow(TIME_TYPE, "gt", "\'00:00:00.000\'", "\"00:00:00.000\"", " > ")] + [DataRow(TIME_TYPE, "gte", "\'10:13:14.123\'", "\"10:13:14.123\"", " >= ")] + [DataRow(TIME_TYPE, "lt", "\'23:59:59.999\'", "\"23:59:59.999\"", " < ")] + [DataRow(TIME_TYPE, "lte", "\'23:59:59.999\'", "\"23:59:59.999\"", " <= ")] + [DataRow(TIME_TYPE, "neq", "\'10:23:54.9999999\'", "\"10:23:54.9999999\"", "!=")] + [DataRow(TIME_TYPE, "eq", "\'10:23:54.9999999\'", "\"10:23:54.9999999\"", "=")] + public async Task QueryTypeColumnFilterAndOrderByLocalTime(string type, string filterOperator, string sqlValue, string gqlValue, string queryOperator) + { + await QueryTypeColumnFilterAndOrderBy(type, filterOperator, sqlValue, gqlValue, queryOperator); + } + [DataTestMethod] [DataRow(BYTE_TYPE, "255")] [DataRow(BYTE_TYPE, "0")] @@ -562,6 +577,12 @@ private static void CompareTimeResults(string actual, string expected) using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); + if (actualJsonDoc.RootElement.ValueKind is JsonValueKind.Array) + { + ValidateArrayResults(actualJsonDoc, expectedJsonDoc, fieldName); + return; + } + string actualTimeString = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); string expectedTimeString = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); @@ -599,6 +620,12 @@ private static void ValidateArrayResults(JsonDocument actualJsonDoc, JsonDocumen { Assert.AreEqual(expectedValue.GetSingle(), actualValue.GetSingle()); } + else if (fieldName.StartsWith(TIME_TYPE.ToLower())) + { + TimeOnly actualTime = TimeOnly.Parse(actualValue.ToString(), CultureInfo.InvariantCulture); + TimeOnly expectedTime = TimeOnly.Parse(expectedValue.ToString(), CultureInfo.InvariantCulture); + Assert.AreEqual(expectedTime.ToLongTimeString(), actualTime.ToLongTimeString()); + } else { Assert.AreEqual(expectedValue.GetDouble(), actualValue.GetDouble()); From 184010587315114ea2152a1c4ed0ea4accf60cac Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Wed, 26 Jul 2023 17:25:58 +0530 Subject: [PATCH 34/50] adding tests for different time format --- .../GraphQLSupportedTypesTestsBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index c3879627c8..67c4f711d6 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -255,6 +255,8 @@ public async Task QueryTypeColumnFilterAndOrderByLocalTime(string type, string f [DataRow(BYTEARRAY_TYPE, "\"U3RyaW5neQ==\"")] [DataRow(TIME_TYPE, "\"23:59:59.9999999\"")] [DataRow(TIME_TYPE, "\"23:59:59\"")] + [DataRow(TIME_TYPE, "\"23:59:59.9\"")] + [DataRow(TIME_TYPE, "\"23:59\"")] [DataRow(TIME_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "\"V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=\"")] [DataRow(BYTEARRAY_TYPE, "null")] @@ -310,6 +312,8 @@ public async Task InsertInvalidIntoTypeColumn(string type, string value) [DataRow(BOOLEAN_TYPE, true)] [DataRow(DATETIMEOFFSET_TYPE, "1999-01-08 10:23:54+8:00")] [DataRow(DATETIME_TYPE, "1999-01-08 10:23:54")] + [DataRow(TIME_TYPE, "\"23:59:59.9999999\"")] + [DataRow(TIME_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")] public async Task InsertIntoTypeColumnWithArgument(string type, object value) { From 4a1937832755f4294f5cda0483bb9eb82b4b5e3c Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 27 Jul 2023 11:58:45 +0530 Subject: [PATCH 35/50] using max dateTime in the test to check no overflow issue --- src/Service.Tests/DatabaseSchema-MsSql.sql | 2 +- src/Service.Tests/DatabaseSchema-MySql.sql | 2 +- src/Service.Tests/DatabaseSchema-PostgreSql.sql | 2 +- .../MsSqlGraphQLPaginationTests.cs | 8 ++++---- .../MySqlGraphQLPaginationTests.cs | 2 +- .../GraphQLSupportedTypesTestsBase.cs | 10 ++++------ 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Service.Tests/DatabaseSchema-MsSql.sql b/src/Service.Tests/DatabaseSchema-MsSql.sql index dbff3a2474..4768723a12 100644 --- a/src/Service.Tests/DatabaseSchema-MsSql.sql +++ b/src/Service.Tests/DatabaseSchema-MsSql.sql @@ -412,7 +412,7 @@ VALUES '0001-01-01', '1753-01-01 00:00:00.000', '0001-01-01 00:00:00.0000000', '0001-01-01 00:00:00.0000000+0:00', '1900-01-01 00:00:00', '00:00:00.0000000', 0x00000000), (4, 255, 32767, 2147483647, 9223372036854775807, 'null', 3.4E38, 1.7E308, 2.929292E-14, 1, - '9998-12-31', '9998-12-31 23:59:59', '9998-12-31 23:59:59.9999999', '9998-12-31 23:59:59.9999999+00:00', '2079-06-06', '23:59:59.9999999', + '9999-12-31', '9999-12-31 23:59:59', '9999-12-31 23:59:59.9999999', '9999-12-31 23:59:59.9999999+14:00', '2079-06-06', '23:59:59.9999999', 0xFFFFFFFF), (5, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); SET IDENTITY_INSERT type_table OFF diff --git a/src/Service.Tests/DatabaseSchema-MySql.sql b/src/Service.Tests/DatabaseSchema-MySql.sql index 034041d70a..ecc27255c1 100644 --- a/src/Service.Tests/DatabaseSchema-MySql.sql +++ b/src/Service.Tests/DatabaseSchema-MySql.sql @@ -334,7 +334,7 @@ INSERT INTO type_table(id, byte_types, short_types, int_types, long_types, strin (1, 1, 1, 1, 1, '', 0.33, 0.33, 0.333333, true, '1999-01-08 10:23:54', 0xABCDEF0123), (2, 0, -1, -1, -1, 'lksa;jdflasdf;alsdflksdfkldj', -9.2, -9.2, -9.292929, false, '1999-01-08 10:23:00', 0x98AB7511AABB1234), (3, 0, -32768, -2147483648, -9223372036854775808, '', -3.4E38, -1.7E308, 2.929292E-19, true, '1753-01-01 00:00:00.000', 0x00000000), - (4, 255, 32767, 2147483647, 9223372036854775807, 'null', 3.4E38, 1.7E308, 2.929292E-14, true, '9998-12-31 23:59:59', 0xFFFFFFFF), + (4, 255, 32767, 2147483647, 9223372036854775807, 'null', 3.4E38, 1.7E308, 2.929292E-14, true, '9999-12-31 23:59:59', 0xFFFFFFFF), (5, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); INSERT INTO trees(treeId, species, region, height) VALUES (1, 'Tsuga terophylla', 'Pacific Northwest', '30m'), (2, 'Pseudotsuga menziesii', 'Pacific Northwest', '40m'); INSERT INTO fungi(speciesid, region) VALUES (1, 'northeast'), (2, 'southwest'); diff --git a/src/Service.Tests/DatabaseSchema-PostgreSql.sql b/src/Service.Tests/DatabaseSchema-PostgreSql.sql index 091c5958b6..2a01af8c79 100644 --- a/src/Service.Tests/DatabaseSchema-PostgreSql.sql +++ b/src/Service.Tests/DatabaseSchema-PostgreSql.sql @@ -326,7 +326,7 @@ INSERT INTO type_table(id, short_types, int_types, long_types, string_types, sin (1, 1, 1, 1, '', 0.33, 0.33, 0.333333, true, '1999-01-08 10:23:54', '\xABCDEF0123'), (2, -1, -1, -1, 'lksa;jdflasdf;alsdflksdfkldj', -9.2, -9.2, -9.292929, false, '19990108 10:23:00', '\x98AB7511AABB1234'), (3, -32768, -2147483648, -9223372036854775808, '', -3.4E38, -1.7E308, 2.929292E-19, true, '1753-01-01 00:00:00.000', '\x00000000'), - (4, 32767, 2147483647, 9223372036854775807, 'null', 3.4E38, 1.7E308, 2.929292E-14, true, '9998-12-31 23:59:59.997', '\xFFFFFFFF'), + (4, 32767, 2147483647, 9223372036854775807, 'null', 3.4E38, 1.7E308, 2.929292E-14, true, '9999-12-31 23:59:59.997', '\xFFFFFFFF'), (5, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); INSERT INTO trees("treeId", species, region, height) VALUES (1, 'Tsuga terophylla', 'Pacific Northwest', '30m'), (2, 'Pseudotsuga menziesii', 'Pacific Northwest', '40m'); INSERT INTO fungi(speciesid, region) VALUES (1, 'northeast'), (2, 'southwest'); diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs index 0a5ce1938c..a05c8d9829 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs @@ -44,16 +44,16 @@ public static async Task SetupAsync(TestContext context) [DataRow("boolean_types", "false", "true", 2, 4, DisplayName = "Test after token for boolean values.")] [DataRow("date_types", "\"1753-01-01\"", - "\"9998-12-31\"", 3, 4, + "\"9999-12-31\"", 3, 4, DisplayName = "Test after token for date values.")] [DataRow("datetime_types", "\"1753-01-01T00:00:00.000\"", - "\"9998-12-31T23:59:59\"", 3, 4, + "\"9999-12-31T23:59:59\"", 3, 4, DisplayName = "Test after token for datetime values.")] [DataRow("datetime2_types", "\"1753-01-01 00:00:00.0000000\"", - "\"9998-12-31T23:59:59.9999999\"", 3, 4, + "\"9999-12-31T23:59:59.9999999\"", 3, 4, DisplayName = "Test after token for datetime2 values.")] [DataRow("datetimeoffset_types", "\"0001-01-01 00:00:00.0000000+0:00\"", - "\"9998-12-31T23:59:59.9999999+00:00\"", 3, 4, + "\"9999-12-31T23:59:59.9999999+14:00\"", 3, 4, DisplayName = "Test after token for datetimeoffset values.")] [DataRow("smalldatetime_types", "\"1900-01-01 00:00:00\"", "\"2079-06-06T00:00:00\"", 3, 4, diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs index d6aa33b291..3095265b60 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MySqlGraphQLPaginationTests.cs @@ -44,7 +44,7 @@ public static async Task SetupAsync(TestContext context) [DataRow("boolean_types", "false", "true", 2, 4, DisplayName = "Test after token for boolean values.")] [DataRow("datetime_types", "\"1753-01-01T00:00:00.000\"", - "\"9998-12-31 23:59:59.000000\"", 3, 4, + "\"9999-12-31 23:59:59.000000\"", 3, 4, DisplayName = "Test after token for datetime values.")] [DataRow("bytearray_types", "\"AAAAAA==\"", "\"/////w==\"", 3, 4, DisplayName = "Test after token for bytearray values.")] diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index 67c4f711d6..44ad7aa5e1 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -167,8 +167,6 @@ public async Task QueryTypeColumnFilterAndOrderBy(string type, string filterOper /// /// Separate test case for DateTime to allow overwrite for postgreSql. - /// Year 9998 used in test and data within test tables to avoid out of - /// date range error within GQL. /// [DataTestMethod] [DataRow(DATETIME_TYPE, "gt", "\'1999-01-08\'", "\"1999-01-08\"", " > ")] @@ -179,14 +177,14 @@ public async Task QueryTypeColumnFilterAndOrderBy(string type, string filterOper [DataRow(DATETIME_TYPE, "eq", "\'1999-01-08\'", "\"1999-01-08T01:01:01\"", "=")] [DataRow(DATETIME_TYPE, "gt", "\'1999-01-08 10:23:00\'", "\"1999-01-08 10:23:00\"", " > ")] [DataRow(DATETIME_TYPE, "gte", "\'1999-01-08 10:23:00\'", "\"1999-01-08 10:23:00\"", " >= ")] - [DataRow(DATETIME_TYPE, "lt", "\'9998-12-31 23:59:59\'", "\"9998-12-31 23:59:59\"", " < ")] - [DataRow(DATETIME_TYPE, "lte", "\'9998-12-31 23:59:59\'", "\"9998-12-31 23:59:59\"", " <= ")] + [DataRow(DATETIME_TYPE, "lt", "\'9999-12-31 23:59:59\'", "\"9999-12-31 23:59:59\"", " < ")] + [DataRow(DATETIME_TYPE, "lte", "\'9999-12-31 23:59:59\'", "\"9999-12-31 23:59:59\"", " <= ")] [DataRow(DATETIME_TYPE, "neq", "\'1999-01-08 10:23:00\'", "\"1999-01-08 10:23:00\"", "!=")] [DataRow(DATETIME_TYPE, "eq", "\'1999-01-08 10:23:00\'", "\"1999-01-08 10:23:00\"", "=")] [DataRow(DATETIME_TYPE, "gt", "\'1999-01-08 10:23:00.9999999\'", "\"1999-01-08 10:23:00.9999999\"", " > ")] [DataRow(DATETIME_TYPE, "gte", "\'1999-01-08 10:23:00.9999999\'", "\"1999-01-08 10:23:00.9999999\"", " >= ")] - [DataRow(DATETIME_TYPE, "lt", "\'9998-12-31 23:59:59.9999999\'", "\"9998-12-31 23:59:59.9999999\"", " < ")] - [DataRow(DATETIME_TYPE, "lte", "\'9998-12-31 23:59:59.9999999\'", "\"9998-12-31 23:59:59.9999999\"", " <= ")] + [DataRow(DATETIME_TYPE, "lt", "\'9999-12-31 23:59:59.9999999\'", "\"9999-12-31 23:59:59.9999999\"", " < ")] + [DataRow(DATETIME_TYPE, "lte", "\'9999-12-31 23:59:59.9999999\'", "\"9999-12-31 23:59:59.9999999\"", " <= ")] [DataRow(DATETIME_TYPE, "neq", "\'1999-01-08 10:23:00.9999999\'", "\"1999-01-08 10:23:00.9999999\"", "!=")] [DataRow(DATETIME_TYPE, "eq", "\'1999-01-08 10:23:00.9999999\'", "\"1999-01-08 10:23:00.9999999\"", "=")] [DataRow(DATETIME_TYPE, "neq", "\'1999-01-08 10:23:54.9999999-14:00\'", "\"1999-01-08 10:23:54.9999999-14:00\"", "!=")] From a6fa28f30ee940c86cc72e7f9bc2b254a25459fd Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 27 Jul 2023 12:24:39 +0530 Subject: [PATCH 36/50] fixing PGSQL tests --- .../PostgreSqlGraphQLPaginationTests.cs | 2 +- src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs index 60bbece479..ae58ed8f5c 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/PostgreSqlGraphQLPaginationTests.cs @@ -43,7 +43,7 @@ public static async Task SetupAsync(TestContext context) [DataRow("boolean_types", "false", "true", 2, 4, DisplayName = "Test after token for boolean values.")] [DataRow("datetime_types", "\"1753-01-01T00:00:00.000\"", - "\"9998-12-31T23:59:59.997\"", 3, 4, + "\"9999-12-31T23:59:59.997\"", 3, 4, DisplayName = "Test after token for datetime values.")] [DataRow("bytearray_types", "\"AAAAAA==\"", "\"/////w==\"", 3, 4, DisplayName = "Test after token for bytearray values.")] diff --git a/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs b/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs index 69722a3825..c58386f66e 100644 --- a/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs +++ b/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs @@ -59,9 +59,9 @@ public static async Task SetupAsync(TestContext context) DisplayName = "Equate date types.")] [DataRow("datetime_types eq 2023-01-24T12:51:59Z", "([datetime_types] = @param1)", DisplayName = "Equate datetime types.")] - [DataRow("datetime2_types eq 9998-12-31T21:59:59.99999Z", "([datetime2_types] = @param1)", + [DataRow("datetime2_types eq 9999-12-31T21:59:59.99999Z", "([datetime2_types] = @param1)", DisplayName = "Equate datetime2 types.")] - [DataRow("datetimeoffset_types eq 9998-12-31T21:59:59.99999-14:00", + [DataRow("datetimeoffset_types eq 9999-12-31T21:59:59.99999-14:00", "([datetimeoffset_types] = @param1)", DisplayName = "Equate datetimeoffset types.")] [DataRow("smalldatetime_types eq 2079-06-06", "([smalldatetime_types] = @param1)", DisplayName = "Equate smalldatetime types.")] From a9c396df1960b1bd1f0e6dbff4c523d1827e6860 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 27 Jul 2023 14:24:44 +0530 Subject: [PATCH 37/50] fixing tests --- src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs b/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs index c58386f66e..69722a3825 100644 --- a/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs +++ b/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs @@ -59,9 +59,9 @@ public static async Task SetupAsync(TestContext context) DisplayName = "Equate date types.")] [DataRow("datetime_types eq 2023-01-24T12:51:59Z", "([datetime_types] = @param1)", DisplayName = "Equate datetime types.")] - [DataRow("datetime2_types eq 9999-12-31T21:59:59.99999Z", "([datetime2_types] = @param1)", + [DataRow("datetime2_types eq 9998-12-31T21:59:59.99999Z", "([datetime2_types] = @param1)", DisplayName = "Equate datetime2 types.")] - [DataRow("datetimeoffset_types eq 9999-12-31T21:59:59.99999-14:00", + [DataRow("datetimeoffset_types eq 9998-12-31T21:59:59.99999-14:00", "([datetimeoffset_types] = @param1)", DisplayName = "Equate datetimeoffset types.")] [DataRow("smalldatetime_types eq 2079-06-06", "([smalldatetime_types] = @param1)", DisplayName = "Equate smalldatetime types.")] From bdb5fcceb9babec4d2b1c940085179c6b9c91e23 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 27 Jul 2023 17:50:18 +0530 Subject: [PATCH 38/50] fixing mysql tests --- .../GraphQLSupportedTypesTestsBase.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index 44ad7aa5e1..473d9c68a0 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -197,6 +197,13 @@ public async Task QueryTypeColumnFilterAndOrderBy(string type, string filterOper [DataRow(DATETIME_TYPE, "eq", "\'1999-01-08 10:23:54\'", "\"1999-01-08 10:23:54\"", "=")] public async Task QueryTypeColumnFilterAndOrderByDateTime(string type, string filterOperator, string sqlValue, string gqlValue, string queryOperator) { + // In MySQL, the DATETIME data type supports a range from '1000-01-01 00:00:00.0000000' to '9999-12-31 23:59:59.0000000' + if (DatabaseEngine is TestCategory.MYSQL && sqlValue is "\'9999-12-31 23:59:59.9999999\'") + { + sqlValue = "\'9999-12-31 23:59:59.0000000\'"; + gqlValue = "\"9999-12-31 23:59:59.0000000\""; + } + await QueryTypeColumnFilterAndOrderBy(type, filterOperator, sqlValue, gqlValue, queryOperator); } From a38db0ec6b41e3e363e6592a1e2d9e85c92cc788 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 27 Jul 2023 23:59:31 +0530 Subject: [PATCH 39/50] adding more tests --- .../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 473d9c68a0..9b3b7e9e00 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -289,6 +289,9 @@ public async Task InsertIntoTypeColumn(string type, string value) [DataTestMethod] [DataRow(TIME_TYPE, "\"32:59:59.9999999\"")] [DataRow(TIME_TYPE, "\"22:67:59.9999999\"")] + [DataRow(TIME_TYPE, "\"-22:67:59.9999999\"")] + [DataRow(TIME_TYPE, "\"22:-67:59.9999999\"")] + [DataRow(TIME_TYPE, "\"22:67:59.-9999999\"")] public async Task InsertInvalidIntoTypeColumn(string type, string value) { if (!IsSupportedType(type)) From dec4d154f8fdd060528b965a58c8a82b0884623e Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 31 Jul 2023 14:25:02 +0530 Subject: [PATCH 40/50] nit fixes --- .../Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs | 4 ++-- src/Core/Services/TypeHelper.cs | 2 -- .../GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs b/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs index 2cf9ca3ff8..b3c89b9004 100644 --- a/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs +++ b/src/Core/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs @@ -361,8 +361,8 @@ protected static object ParseParamAsSystemType(string param, Type systemType) "Double" => double.Parse(param), "Decimal" => decimal.Parse(param), "Boolean" => bool.Parse(param), - "DateTime" => DateTime.Parse(param, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), - "DateTimeOffset" => DateTimeOffset.Parse(param), + "DateTime" => DateTimeOffset.Parse(param, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), + "DateTimeOffset" => DateTimeOffset.Parse(param, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal), "Date" => DateOnly.Parse(param), "Guid" => Guid.Parse(param), "TimeOnly" => TimeOnly.Parse(param), diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index cf31bb2434..7abc229a34 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -36,8 +36,6 @@ public static class TypeHelper [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, [typeof(byte[])] = DbType.Binary, - [typeof(DateTime)] = DbType.DateTime, - [typeof(DateTimeOffset)] = DbType.DateTimeOffset, [typeof(TimeOnly)] = DbType.Time, [typeof(TimeSpan)] = DbType.Time, [typeof(object)] = DbType.Object diff --git a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs index a05c8d9829..1a958ce783 100644 --- a/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs +++ b/src/Service.Tests/SqlTests/GraphQLPaginationTests/MsSqlGraphQLPaginationTests.cs @@ -43,13 +43,13 @@ public static async Task SetupAsync(TestContext context) DisplayName = "Test after token for decimal values.")] [DataRow("boolean_types", "false", "true", 2, 4, DisplayName = "Test after token for boolean values.")] - [DataRow("date_types", "\"1753-01-01\"", + [DataRow("date_types", "\"0001-01-01\"", "\"9999-12-31\"", 3, 4, DisplayName = "Test after token for date values.")] [DataRow("datetime_types", "\"1753-01-01T00:00:00.000\"", "\"9999-12-31T23:59:59\"", 3, 4, DisplayName = "Test after token for datetime values.")] - [DataRow("datetime2_types", "\"1753-01-01 00:00:00.0000000\"", + [DataRow("datetime2_types", "\"0001-01-01 00:00:00.0000000\"", "\"9999-12-31T23:59:59.9999999\"", 3, 4, DisplayName = "Test after token for datetime2 values.")] [DataRow("datetimeoffset_types", "\"0001-01-01 00:00:00.0000000+0:00\"", From 127af62b2f234121b9f657ccfba21d460d94da02 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 31 Jul 2023 14:29:42 +0530 Subject: [PATCH 41/50] nit fix --- src/Service/Azure.DataApiBuilder.Service.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Service/Azure.DataApiBuilder.Service.csproj b/src/Service/Azure.DataApiBuilder.Service.csproj index 156dcc7436..8338c6c059 100644 --- a/src/Service/Azure.DataApiBuilder.Service.csproj +++ b/src/Service/Azure.DataApiBuilder.Service.csproj @@ -51,7 +51,6 @@ - From 7ff72b889ecd789c4783f608b551b4c61c312f37 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 31 Jul 2023 14:52:51 +0530 Subject: [PATCH 42/50] nit fix --- .../OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs index 3dd726f952..747b2649d0 100644 --- a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs +++ b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs @@ -101,8 +101,6 @@ private static IEnumerable GetTestData_SupportedSystemTypesMapToJsonVa [DataRow(typeof(Guid?))] [DataRow(typeof(TimeOnly?))] [DataRow(typeof(TimeSpan?))] - [DataRow(typeof(DateTime?))] - [DataRow(typeof(DateTimeOffset?))] [DataTestMethod] public void ResolveUnderlyingTypeForNullableValueType(Type nullableType) { From 2b55cd416d1c400c185d50645c94f0ce1eacacce Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 31 Jul 2023 22:07:41 +0530 Subject: [PATCH 43/50] updating test --- .../GraphQLSupportedTypesTestsBase.cs | 50 ++++++++++++++----- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index 9b3b7e9e00..cf70d269f5 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -189,6 +189,8 @@ public async Task QueryTypeColumnFilterAndOrderBy(string type, string filterOper [DataRow(DATETIME_TYPE, "eq", "\'1999-01-08 10:23:00.9999999\'", "\"1999-01-08 10:23:00.9999999\"", "=")] [DataRow(DATETIME_TYPE, "neq", "\'1999-01-08 10:23:54.9999999-14:00\'", "\"1999-01-08 10:23:54.9999999-14:00\"", "!=")] [DataRow(DATETIME_TYPE, "eq", "\'1999-01-08 10:23:54.9999999-14:00\'", "\"1999-01-08 10:23:54.9999999-14:00\"", "=")] + [DataRow(DATETIMEOFFSET_TYPE, "neq", "\'1999-01-08 10:23:54.9999999-14:00\'", "\"1999-01-08 10:23:54.9999999-14:00\"", "!=")] + [DataRow(DATETIMEOFFSET_TYPE, "eq", "\'1999-01-08 10:23:54.9999999-14:00\'", "\"1999-01-08 10:23:54.9999999-14:00\"", "=")] [DataRow(DATETIME_TYPE, "gt", "\'1999-01-08 10:22:00\'", "\"1999-01-08 10:22:00\"", " > ")] [DataRow(DATETIME_TYPE, "gte", "\'1999-01-08 10:23:54\'", "\"1999-01-08 10:23:54\"", " >= ")] [DataRow(DATETIME_TYPE, "lt", "\'2079-06-06\'", "\"2079-06-06\"", " < ")] @@ -254,6 +256,7 @@ public async Task QueryTypeColumnFilterAndOrderByLocalTime(string type, string f [DataRow(BOOLEAN_TYPE, "false")] [DataRow(BOOLEAN_TYPE, "null")] [DataRow(DATETIMEOFFSET_TYPE, "\"1999-01-08 10:23:54+8:00\"")] + [DataRow(DATETIMEOFFSET_TYPE, "\"1999-01-08 10:23:54.671287+8:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08 09:20:00\"")] [DataRow(DATETIME_TYPE, "\"1999-01-08\"")] [DataRow(DATETIME_TYPE, "null")] @@ -286,9 +289,13 @@ public async Task InsertIntoTypeColumn(string type, string value) await ResetDbStateAsync(); } + /// + /// Test case for invalid time, such as negative values or hours>24 or minutes/seconds>60. + /// [DataTestMethod] [DataRow(TIME_TYPE, "\"32:59:59.9999999\"")] [DataRow(TIME_TYPE, "\"22:67:59.9999999\"")] + [DataRow(TIME_TYPE, "\"14:12:99.9999999\"")] [DataRow(TIME_TYPE, "\"-22:67:59.9999999\"")] [DataRow(TIME_TYPE, "\"22:-67:59.9999999\"")] [DataRow(TIME_TYPE, "\"22:67:59.-9999999\"")] @@ -306,7 +313,6 @@ public async Task InsertInvalidIntoTypeColumn(string type, string value) JsonElement response = await ExecuteGraphQLRequestAsync(gqlQuery, graphQLQueryName, isAuthenticated: true); string responseMessage = Regex.Unescape(JsonSerializer.Serialize(response)); Assert.IsTrue(responseMessage.Contains($"{value} cannot be resolved as column \"{field}\" with type \"TimeSpan\".")); - await ResetDbStateAsync(); } [DataTestMethod] @@ -555,7 +561,7 @@ private static void CompareDateTimeResults(string actual, string expected) } /// - /// Required due to different format between mysql datetimeoffset and HotChocolate datetime + /// Required due to different format between sql datetimeoffset and HotChocolate datetime /// result /// private static void CompareDateTimeOffsetResults(string actual, string expected) @@ -565,22 +571,32 @@ private static void CompareDateTimeOffsetResults(string actual, string expected) using JsonDocument actualJsonDoc = JsonDocument.Parse(actual); using JsonDocument expectedJsonDoc = JsonDocument.Parse(expected); - string actualDateTimeOffset = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); - string expectedDateTimeOffset = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); + if (actualJsonDoc.RootElement.ValueKind is JsonValueKind.Array) + { + ValidateArrayResults(actualJsonDoc, expectedJsonDoc, fieldName); + return; + } + + string actualDateTimeOffsetString = actualJsonDoc.RootElement.GetProperty(fieldName).ToString(); + string expectedDateTimeOffsetString = expectedJsonDoc.RootElement.GetProperty(fieldName).ToString(); // handles cases when one of the values is null - if (string.IsNullOrEmpty(actualDateTimeOffset) || string.IsNullOrEmpty(expectedDateTimeOffset)) + if (string.IsNullOrEmpty(actualDateTimeOffsetString) || string.IsNullOrEmpty(expectedDateTimeOffsetString)) { - Assert.AreEqual(expectedDateTimeOffset, actualDateTimeOffset); + Assert.AreEqual(expectedDateTimeOffsetString, actualDateTimeOffsetString); } else { - Assert.AreEqual(DateTimeOffset.Parse(expectedDateTimeOffset), DateTimeOffset.Parse(actualDateTimeOffset)); + DateTimeOffset actualDateTimeOffset = DateTimeOffset.Parse(actualDateTimeOffsetString, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); + DateTimeOffset expectedDateTimeOffset = DateTimeOffset.Parse(expectedDateTimeOffsetString, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); + Assert.AreEqual(actualDateTimeOffset.ToString(), expectedDateTimeOffset.ToString()); + // Comparing for Miliseconds separetly since Hotcholate respond with only 3 decimal places. + Assert.AreEqual(actualDateTimeOffset.Millisecond, expectedDateTimeOffset.Millisecond); } } /// - /// Required due to different format between SQL time and HotChocolate TimeSpan time(ISO-8601) result. + /// Compares the value from SQL time and HotChocolate LocalTime. /// private static void CompareTimeResults(string actual, string expected) { @@ -605,8 +621,8 @@ private static void CompareTimeResults(string actual, string expected) } else { - TimeOnly actualTime = TimeOnly.Parse(actualTimeString, CultureInfo.InvariantCulture); - TimeOnly expectedTime = TimeOnly.Parse(expectedTimeString, CultureInfo.InvariantCulture); + TimeOnly actualTime = TimeOnly.Parse(actualTimeString); + TimeOnly expectedTime = TimeOnly.Parse(expectedTimeString); Assert.AreEqual(expectedTime.ToLongTimeString(), actualTime.ToLongTimeString()); } } @@ -621,7 +637,15 @@ private static void ValidateArrayResults(JsonDocument actualJsonDoc, JsonDocumen actualElement.TryGetProperty(fieldName, out JsonElement actualValue); expectedElement.TryGetProperty(fieldName, out JsonElement expectedValue); - if (fieldName.StartsWith(DATETIME_TYPE.ToLower())) + if (fieldName.StartsWith(DATETIMEOFFSET_TYPE.ToLower())) + { + DateTimeOffset actualDateTimeOffset = DateTimeOffset.Parse(actualValue.ToString(), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); + DateTimeOffset expectedDateTimeOffset = DateTimeOffset.Parse(expectedValue.ToString(), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); + Assert.AreEqual(actualDateTimeOffset.ToString(), expectedDateTimeOffset.ToString()); + // Comparing for Miliseconds separetly since Hotcholate respond with only 3 decimal places. + Assert.AreEqual(actualDateTimeOffset.Millisecond, expectedDateTimeOffset.Millisecond); + } + else if (fieldName.StartsWith(DATETIME_TYPE.ToLower())) { // MySql returns a format that will not directly parse into DateTime type so we use string here for parsing DateTime actualDateTime = DateTime.Parse(actualValue.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); @@ -634,8 +658,8 @@ private static void ValidateArrayResults(JsonDocument actualJsonDoc, JsonDocumen } else if (fieldName.StartsWith(TIME_TYPE.ToLower())) { - TimeOnly actualTime = TimeOnly.Parse(actualValue.ToString(), CultureInfo.InvariantCulture); - TimeOnly expectedTime = TimeOnly.Parse(expectedValue.ToString(), CultureInfo.InvariantCulture); + TimeOnly actualTime = TimeOnly.Parse(actualValue.ToString()); + TimeOnly expectedTime = TimeOnly.Parse(expectedValue.ToString()); Assert.AreEqual(expectedTime.ToLongTimeString(), actualTime.ToLongTimeString()); } else From a05134b6db74a00e3b6bf517df102daaeb57f250 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 1 Aug 2023 11:25:05 +0530 Subject: [PATCH 44/50] added OData tests as well --- src/Core/Parsers/ODataASTVisitor.cs | 2 ++ src/Core/Services/TypeHelper.cs | 1 - src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Core/Parsers/ODataASTVisitor.cs b/src/Core/Parsers/ODataASTVisitor.cs index 829602f677..901de39935 100644 --- a/src/Core/Parsers/ODataASTVisitor.cs +++ b/src/Core/Parsers/ODataASTVisitor.cs @@ -143,6 +143,8 @@ private static object GetParamWithSystemType(string param, IEdmTypeReference edm return DateTimeOffset.Parse(param); case EdmPrimitiveTypeKind.String: return param; + case EdmPrimitiveTypeKind.TimeOfDay: + return TimeOnly.Parse(param); default: // should never happen due to the config being validated for correct types throw new NotSupportedException($"{edmType} is not supported"); diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index 7abc229a34..02ed06aee2 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -134,7 +134,6 @@ public static EdmPrimitiveTypeKind GetEdmPrimitiveTypeFromSystemType(Type column "DateTime" => EdmPrimitiveTypeKind.DateTimeOffset, "DateTimeOffset" => EdmPrimitiveTypeKind.DateTimeOffset, "Date" => EdmPrimitiveTypeKind.Date, - "TimeOnly" => EdmPrimitiveTypeKind.TimeOfDay, "TimeSpan" => EdmPrimitiveTypeKind.TimeOfDay, _ => throw new ArgumentException($"Column type" + $" {columnSystemType.Name} not yet supported.") diff --git a/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs b/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs index 69722a3825..d34737f0ca 100644 --- a/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs +++ b/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs @@ -67,6 +67,8 @@ public static async Task SetupAsync(TestContext context) DisplayName = "Equate smalldatetime types.")] [DataRow("bytearray_types eq 1000", "([bytearray_types] = @param1)", DisplayName = "Equate bytearray types.")] [DataRow("guid_types eq 9A19103F-16F7-4668-BE54-9A1E7A4F7556", "([guid_types] = @param1)", DisplayName = "Equate guid types.")] + [DataRow("time_types eq 10:23:54.9999999", "([time_types] = @param1)", DisplayName = "Equate time types.")] + [DataRow("time_types eq null", "([time_types] IS NULL)", DisplayName = "Equate time types for null.")] [TestMethod] public void VisitorLeftFieldRightConstantFilterTest(string filterExp, string expectedPredicate) { From de2f48ad773686b4953f3d91d3c77fd548ef4266 Mon Sep 17 00:00:00 2001 From: abhishekkumams <102276754+abhishekkumams@users.noreply.github.com> Date: Fri, 4 Aug 2023 15:14:24 +0530 Subject: [PATCH 45/50] Update src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs Co-authored-by: Sean Leonard --- .../GraphQLSupportedTypesTestsBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index cf70d269f5..81ea5d4a2e 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -590,7 +590,7 @@ private static void CompareDateTimeOffsetResults(string actual, string expected) DateTimeOffset actualDateTimeOffset = DateTimeOffset.Parse(actualDateTimeOffsetString, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); DateTimeOffset expectedDateTimeOffset = DateTimeOffset.Parse(expectedDateTimeOffsetString, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); Assert.AreEqual(actualDateTimeOffset.ToString(), expectedDateTimeOffset.ToString()); - // Comparing for Miliseconds separetly since Hotcholate respond with only 3 decimal places. + // Comparing for milliseconds separately since HotChocolate time type is resolved only to 3 decimal places. Assert.AreEqual(actualDateTimeOffset.Millisecond, expectedDateTimeOffset.Millisecond); } } From 9676493cf94a84a244da2b25e21b3fac9a23defa Mon Sep 17 00:00:00 2001 From: abhishekkumams <102276754+abhishekkumams@users.noreply.github.com> Date: Fri, 4 Aug 2023 15:14:44 +0530 Subject: [PATCH 46/50] Update src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs Co-authored-by: Sean Leonard --- .../GraphQLSupportedTypesTestsBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index 81ea5d4a2e..5978ef9659 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -642,7 +642,7 @@ private static void ValidateArrayResults(JsonDocument actualJsonDoc, JsonDocumen DateTimeOffset actualDateTimeOffset = DateTimeOffset.Parse(actualValue.ToString(), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); DateTimeOffset expectedDateTimeOffset = DateTimeOffset.Parse(expectedValue.ToString(), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); Assert.AreEqual(actualDateTimeOffset.ToString(), expectedDateTimeOffset.ToString()); - // Comparing for Miliseconds separetly since Hotcholate respond with only 3 decimal places. + // Comparing for milliseconds separately since HotChocolate time type is resolved only to 3 decimal places. Assert.AreEqual(actualDateTimeOffset.Millisecond, expectedDateTimeOffset.Millisecond); } else if (fieldName.StartsWith(DATETIME_TYPE.ToLower())) From ecec68e5b41d60373ed869c6760f13dc57b579b6 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Sat, 5 Aug 2023 00:06:21 +0530 Subject: [PATCH 47/50] fix formatting and adding tests --- src/Core/Services/TypeHelper.cs | 5 + .../CLRtoJsonValueTypeUnitTests.cs | 2 +- .../GraphQLSupportedTypesTestsBase.cs | 112 +++++++++++------- .../Unittests/ODataASTVisitorUnitTests.cs | 18 +++ 4 files changed, 94 insertions(+), 43 deletions(-) diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index 02ed06aee2..8ad1feb178 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -17,6 +17,10 @@ public static class TypeHelper { /// /// Maps .NET Framework types to DbType enum + /// Not Adding this hard mapping for System.DateTime/DateTimeOffset, + /// since System.DateTime is used for both SqldbType.DateTime and SqldbType.DateTime2. + /// So no change required here as seperate logic exist which does the mapping + /// and also Hotchocolate only has Hotchocolate.Types.DateTime for SqlDbType.DateTime/DateTime2/DateTimeOffset. /// private static Dictionary _systemTypeToDbTypeMap = new() { @@ -134,6 +138,7 @@ public static EdmPrimitiveTypeKind GetEdmPrimitiveTypeFromSystemType(Type column "DateTime" => EdmPrimitiveTypeKind.DateTimeOffset, "DateTimeOffset" => EdmPrimitiveTypeKind.DateTimeOffset, "Date" => EdmPrimitiveTypeKind.Date, + "TimeOnly" => EdmPrimitiveTypeKind.TimeOfDay, "TimeSpan" => EdmPrimitiveTypeKind.TimeOfDay, _ => throw new ArgumentException($"Column type" + $" {columnSystemType.Name} not yet supported.") diff --git a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs index 747b2649d0..b5964a04d2 100644 --- a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs +++ b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs @@ -76,7 +76,7 @@ private static IEnumerable GetTestData_SupportedSystemTypesMapToJsonVa /// Validates the behavior of TypeHelper.GetJsonDataTypeFromSystemType(Type type) by /// ensuring that a nullable value type like int? is resolved to its underlying type int. /// Consequently, the lookup in the _systemTypeToJsonDataTypeMap and _systemTypeToDbTypeMap - /// dictionary succeeds without requiring nullable value type be defined as keys. + /// dictionary succeeds without requiring nullable value types be defined as keys. /// Nullable value types are represented in runtime as Nullable. Whereas /// nullable reference types do no have a standalone runtime representation. /// See csharplang discussion on why typeof(string?) (nullable reference type) is not valid, diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index cf70d269f5..e03ffabe5e 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -167,6 +167,8 @@ public async Task QueryTypeColumnFilterAndOrderBy(string type, string filterOper /// /// Separate test case for DateTime to allow overwrite for postgreSql. + /// The method constructs a GraphQL query to filter and order the datetime column based on the given parameters. + /// The test checks various datetime data types such as datetime, datetimeoffset, and time. /// [DataTestMethod] [DataRow(DATETIME_TYPE, "gt", "\'1999-01-08\'", "\"1999-01-08\"", " > ")] @@ -210,7 +212,7 @@ public async Task QueryTypeColumnFilterAndOrderByDateTime(string type, string fi } /// - /// Test case for LocalTime filters. + /// Validates that usage of LocalTime values with comparison operators in GraphQL filters results in the expected filtered result set. /// [DataTestMethod] [DataRow(TIME_TYPE, "gt", "\'00:00:00.000\'", "\"00:00:00.000\"", " > ")] @@ -224,6 +226,33 @@ public async Task QueryTypeColumnFilterAndOrderByLocalTime(string type, string f await QueryTypeColumnFilterAndOrderBy(type, filterOperator, sqlValue, gqlValue, queryOperator); } + /// + /// Validates that LocalTime values with X precision are handled correctly: precision of 7 decimal places used with eq (=) will + /// not return result with only 3 decimal places i.e. 10:23:54.999 != 10:23:54.9999999 + /// In the Database only one row exist with value 23:59:59.9999999 + /// + [DataTestMethod] + [DataRow("\"23:59:59.9999999\"", 1, DisplayName = "TimeType Precision Check with 7 decimal places")] + [DataRow("\"23:59:59.999\"", 0, DisplayName = "TimeType Precision Check with 3 decimal places")] + public async Task TestTimeTypePrecisionCheck(string gqlValue, int count) + { + string graphQLQueryName = "supportedTypes"; + string gqlQuery = @"{ + supportedTypes(first: 100 orderBy: { " + "time_types" + ": ASC } filter: { " + "time_types" + ": {" + "eq" + ": " + gqlValue + @"} }) { + items { + " + "time_types" + @" + } + } + }"; + + JsonElement gqlResponse = await ExecuteGraphQLRequestAsync(gqlQuery, graphQLQueryName, isAuthenticated: false); + Assert.AreEqual(count, gqlResponse.GetProperty("items").GetArrayLength()); + } + + /// + /// the method constructs a GraphQL query to insert the value into the database table + /// and then executes the query and compares the expected result with the actual result to verify if different types are supported. + /// [DataTestMethod] [DataRow(BYTE_TYPE, "255")] [DataRow(BYTE_TYPE, "0")] @@ -553,10 +582,7 @@ private static void CompareDateTimeResults(string actual, string expected) } else { - // Adjusting to universal, since DateTime doesn't account for TimeZone - DateTime expectedDateTimeUniversal = DateTime.Parse(expectedDateTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); - DateTime actualDateTimeUniversal = DateTime.Parse(actualDateTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); - Assert.AreEqual(expectedDateTimeUniversal, actualDateTimeUniversal); + AssertOnFields(fieldName, actualDateTime, expectedDateTime); } } @@ -587,11 +613,7 @@ private static void CompareDateTimeOffsetResults(string actual, string expected) } else { - DateTimeOffset actualDateTimeOffset = DateTimeOffset.Parse(actualDateTimeOffsetString, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); - DateTimeOffset expectedDateTimeOffset = DateTimeOffset.Parse(expectedDateTimeOffsetString, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); - Assert.AreEqual(actualDateTimeOffset.ToString(), expectedDateTimeOffset.ToString()); - // Comparing for Miliseconds separetly since Hotcholate respond with only 3 decimal places. - Assert.AreEqual(actualDateTimeOffset.Millisecond, expectedDateTimeOffset.Millisecond); + AssertOnFields(fieldName, actualDateTimeOffsetString, expectedDateTimeOffsetString); } } @@ -621,9 +643,7 @@ private static void CompareTimeResults(string actual, string expected) } else { - TimeOnly actualTime = TimeOnly.Parse(actualTimeString); - TimeOnly expectedTime = TimeOnly.Parse(expectedTimeString); - Assert.AreEqual(expectedTime.ToLongTimeString(), actualTime.ToLongTimeString()); + AssertOnFields(fieldName, actualTimeString, expectedTimeString); } } @@ -637,35 +657,43 @@ private static void ValidateArrayResults(JsonDocument actualJsonDoc, JsonDocumen actualElement.TryGetProperty(fieldName, out JsonElement actualValue); expectedElement.TryGetProperty(fieldName, out JsonElement expectedValue); - if (fieldName.StartsWith(DATETIMEOFFSET_TYPE.ToLower())) - { - DateTimeOffset actualDateTimeOffset = DateTimeOffset.Parse(actualValue.ToString(), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); - DateTimeOffset expectedDateTimeOffset = DateTimeOffset.Parse(expectedValue.ToString(), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); - Assert.AreEqual(actualDateTimeOffset.ToString(), expectedDateTimeOffset.ToString()); - // Comparing for Miliseconds separetly since Hotcholate respond with only 3 decimal places. - Assert.AreEqual(actualDateTimeOffset.Millisecond, expectedDateTimeOffset.Millisecond); - } - else if (fieldName.StartsWith(DATETIME_TYPE.ToLower())) - { - // MySql returns a format that will not directly parse into DateTime type so we use string here for parsing - DateTime actualDateTime = DateTime.Parse(actualValue.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); - DateTime expectedDateTime = DateTime.Parse(expectedValue.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); - Assert.AreEqual(expectedDateTime, actualDateTime); - } - else if (fieldName.StartsWith(SINGLE_TYPE.ToLower())) - { - Assert.AreEqual(expectedValue.GetSingle(), actualValue.GetSingle()); - } - else if (fieldName.StartsWith(TIME_TYPE.ToLower())) - { - TimeOnly actualTime = TimeOnly.Parse(actualValue.ToString()); - TimeOnly expectedTime = TimeOnly.Parse(expectedValue.ToString()); - Assert.AreEqual(expectedTime.ToLongTimeString(), actualTime.ToLongTimeString()); - } - else - { - Assert.AreEqual(expectedValue.GetDouble(), actualValue.GetDouble()); - } + AssertOnFields(fieldName, actualValue.ToString(), expectedValue.ToString()); + } + } + + /// + /// Compare given fields from actual and expected json. + /// + private static void AssertOnFields(string field, string actualElement, string expectedElement) + { + if (field.StartsWith(DATETIMEOFFSET_TYPE.ToLower())) + { + DateTimeOffset actualDateTimeOffset = DateTimeOffset.Parse(actualElement.ToString(), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); + DateTimeOffset expectedDateTimeOffset = DateTimeOffset.Parse(expectedElement.ToString(), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal); + Assert.AreEqual(actualDateTimeOffset.ToString(), expectedDateTimeOffset.ToString()); + // Comparing for milliseconds separately since HotChocolate time type is resolved only to 3 decimal places. + Assert.AreEqual(actualDateTimeOffset.Millisecond, expectedDateTimeOffset.Millisecond); + } + else if (field.StartsWith(DATETIME_TYPE.ToLower())) + { + // Adjusting to universal, since DateTime doesn't account for TimeZone + DateTime actualDateTime = DateTime.Parse(actualElement.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + DateTime expectedDateTime = DateTime.Parse(expectedElement.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); + Assert.AreEqual(expectedDateTime, actualDateTime); + } + else if (field.StartsWith(SINGLE_TYPE.ToLower())) + { + Assert.AreEqual(float.Parse(expectedElement), float.Parse(actualElement)); + } + else if (field.StartsWith(TIME_TYPE.ToLower())) + { + TimeOnly actualTime = TimeOnly.Parse(actualElement.ToString()); + TimeOnly expectedTime = TimeOnly.Parse(expectedElement.ToString()); + Assert.AreEqual(expectedTime.ToLongTimeString(), actualTime.ToLongTimeString()); + } + else + { + Assert.AreEqual(double.Parse(expectedElement), double.Parse(actualElement)); } } diff --git a/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs b/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs index d34737f0ca..c440b5b572 100644 --- a/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs +++ b/src/Service.Tests/Unittests/ODataASTVisitorUnitTests.cs @@ -202,6 +202,24 @@ public void InvalidEdmTypeReferenceTest() Assert.ThrowsException(() => visitor.Visit(nodeIn)); } + /// + /// Tests that we throw an exception when trying to use an invalid + /// Time with negative value or time > 24 hours. + /// + [DataTestMethod] + [DataRow("time_types eq 25:23:54.9999999", DisplayName = "Exception thrown with invalid time>24 hrs.")] + [DataRow("time_types eq -13:23:54.9999999", DisplayName = "Exception thrown with invalid time>24 hrs.")] + public void InvalidTimeTypeODataFilterTest(string filterExp) + { + Assert.ThrowsException(() => PerformVisitorTest( + entityName: DEFAULT_ENTITY, + schemaName: DEFAULT_SCHEMA_NAME, + tableName: DEFAULT_TABLE_NAME, + filterString: $"?$filter={filterExp}", + expected: string.Empty + )); + } + /// /// Verifies that we throw an exception for values that can /// not be parsed into a valid Edm Type Kind. Create a constant From 30a1336c3396c1a02cf5ff3c758b60914744ccca Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Sat, 5 Aug 2023 00:25:57 +0530 Subject: [PATCH 48/50] fixing tests --- .../GraphQLSupportedTypesTestsBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index e03ffabe5e..ac151a37a7 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -236,6 +236,11 @@ public async Task QueryTypeColumnFilterAndOrderByLocalTime(string type, string f [DataRow("\"23:59:59.999\"", 0, DisplayName = "TimeType Precision Check with 3 decimal places")] public async Task TestTimeTypePrecisionCheck(string gqlValue, int count) { + if (!IsSupportedType(TIME_TYPE)) + { + Assert.Inconclusive("Type not supported"); + } + string graphQLQueryName = "supportedTypes"; string gqlQuery = @"{ supportedTypes(first: 100 orderBy: { " + "time_types" + ": ASC } filter: { " + "time_types" + ": {" + "eq" + ": " + gqlValue + @"} }) { From f126b8362bfc9070a83ae708286ad5e6bec20ae4 Mon Sep 17 00:00:00 2001 From: abhishekkumams <102276754+abhishekkumams@users.noreply.github.com> Date: Mon, 14 Aug 2023 22:39:21 +0530 Subject: [PATCH 49/50] Update src/Core/Services/TypeHelper.cs Co-authored-by: Aniruddh Munde --- src/Core/Services/TypeHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index 8ad1feb178..9e4c5dd456 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -19,7 +19,7 @@ public static class TypeHelper /// Maps .NET Framework types to DbType enum /// Not Adding this hard mapping for System.DateTime/DateTimeOffset, /// since System.DateTime is used for both SqldbType.DateTime and SqldbType.DateTime2. - /// So no change required here as seperate logic exist which does the mapping + /// So no change required here as separate logic exist which does the mapping /// and also Hotchocolate only has Hotchocolate.Types.DateTime for SqlDbType.DateTime/DateTime2/DateTimeOffset. /// private static Dictionary _systemTypeToDbTypeMap = new() From e68e207da4bc5a1204577a7f2ed93afa77af5032 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 17 Aug 2023 10:21:09 +0530 Subject: [PATCH 50/50] fix formatting --- src/Core/Services/TypeHelper.cs | 9 +++++---- .../GraphQLSupportedTypesTestsBase.cs | 7 ++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index 9e4c5dd456..2bd6d448af 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -17,10 +17,10 @@ public static class TypeHelper { /// /// Maps .NET Framework types to DbType enum - /// Not Adding this hard mapping for System.DateTime/DateTimeOffset, - /// since System.DateTime is used for both SqldbType.DateTime and SqldbType.DateTime2. - /// So no change required here as separate logic exist which does the mapping - /// and also Hotchocolate only has Hotchocolate.Types.DateTime for SqlDbType.DateTime/DateTime2/DateTimeOffset. + /// Not Adding a hard mapping for System.DateTime to DbType.DateTime as + /// Hotchocolate only has Hotchocolate.Types.DateTime for DbType.DateTime/DateTime2/DateTimeOffset, + /// which throws error when inserting/updating dateTime values due to type mismatch. + /// Therefore, seperate logic exists for proper mapping conversion in BaseSqlQueryStructure. /// private static Dictionary _systemTypeToDbTypeMap = new() { @@ -39,6 +39,7 @@ public static class TypeHelper [typeof(string)] = DbType.String, [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, + [typeof(DateTimeOffset)] = DbType.DateTimeOffset, [typeof(byte[])] = DbType.Binary, [typeof(TimeOnly)] = DbType.Time, [typeof(TimeSpan)] = DbType.Time, diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index ac151a37a7..04e2306f3e 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -333,7 +333,7 @@ public async Task InsertIntoTypeColumn(string type, string value) [DataRow(TIME_TYPE, "\"-22:67:59.9999999\"")] [DataRow(TIME_TYPE, "\"22:-67:59.9999999\"")] [DataRow(TIME_TYPE, "\"22:67:59.-9999999\"")] - public async Task InsertInvalidIntoTypeColumn(string type, string value) + public async Task InsertInvalidTimeIntoTimeTypeColumn(string type, string value) { if (!IsSupportedType(type)) { @@ -349,6 +349,11 @@ public async Task InsertInvalidIntoTypeColumn(string type, string value) Assert.IsTrue(responseMessage.Contains($"{value} cannot be resolved as column \"{field}\" with type \"TimeSpan\".")); } + /// + /// The code contains test rows that are used to test the insertion of various data types into a database table using GraphQL, + /// where the parameter values are passed as GraphQL request Variables. The test supports various data types such as byte, short, + /// int, long, string, float, decimal, boolean, datetimeoffset, datetime, time, and bytearray. + /// [DataTestMethod] [DataRow(BYTE_TYPE, 255)] [DataRow(SHORT_TYPE, 30000)]