From d2e7cded7b38ef858481def31ccbafbb0186b0d1 Mon Sep 17 00:00:00 2001 From: Samuel Abraham Date: Tue, 19 Mar 2024 07:29:45 -0700 Subject: [PATCH] v8.2.0 --- .../Extensions/GraphQLExtensions.cs | 152 +++---- .../Extensions/QueryArgumentsExtensions.cs | 22 +- .../Extensions/SchemaExtensions.cs | 25 +- .../Resolvers/FieldResolver.cs | 8 +- .../Resolvers/PropertyFieldResolver.cs | 16 +- .../Resolvers/SqlApiInsertFieldResolver.cs | 38 +- .../Resolvers/SqlApiSelectFieldResolver.cs | 8 +- src/TypeCache.GraphQL/SqlApi/DataResponse.cs | 64 +-- .../SqlApi/OutputResponse.cs | 37 +- .../SqlApi/SelectResponse.cs | 30 +- .../TypeCache.GraphQL.csproj | 2 +- .../Types/GraphQLInputType.cs | 8 +- src/TypeCache.Web/Handlers/SqlApiHandler.cs | 86 ++-- src/TypeCache.Web/TypeCache.Web.csproj | 2 +- .../Attributes/ServiceLifetimeAttribute.cs | 10 +- src/TypeCache/Data/ColumnSchema.cs | 24 +- src/TypeCache/Data/DataSource.cs | 90 ++-- src/TypeCache/Data/DatabaseObject.cs | 30 -- .../Data/Extensions/SqlExtensions.cs | 17 +- src/TypeCache/Data/IDataSource.cs | 15 +- src/TypeCache/Data/ObjectSchema.cs | 50 ++- src/TypeCache/Data/ParameterSchema.cs | 15 +- src/TypeCache/Data/SelectQuery.cs | 16 +- .../Extensions/DateTimeExtensions.cs | 8 +- .../Extensions/DictionaryExtensions.cs | 5 +- src/TypeCache/Extensions/EnumExtensions.cs | 23 +- .../Extensions/EnumerableExtensions.cs | 14 +- .../ReflectionExtensions.Handles.cs | 2 +- .../ReflectionExtensions.MethodBase.cs | 2 +- .../ReflectionExtensions.ObjectType.cs | 2 +- .../ReflectionExtensions.ParameterInfo.cs | 3 +- .../ReflectionExtensions.ScalarType.cs | 32 +- .../Extensions/ReflectionExtensions.Type.cs | 10 +- .../Extensions/RulesBuilderExtensions.cs | 34 -- .../Extensions/ServiceCollectionExtensions.cs | 404 +++++++++++++++++- .../Extensions/StringBuilderExtensions.cs | 2 +- src/TypeCache/Extensions/StringExtensions.cs | 83 ++-- src/TypeCache/Mediation/IMediator.cs | 37 +- src/TypeCache/Mediation/Mediator.cs | 160 +++++-- src/TypeCache/Mediation/RulesBuilder.cs | 258 ----------- src/TypeCache/TypeCache.csproj | 2 +- src/TypeCache/Utilities/RegexCache.cs | 19 - src/TypeCache/Utilities/ValueConverter.cs | 342 ++++++++------- tests/TypeCache.GraphQL.TestApp/Program.cs | 3 +- .../Data/Extensions/SqlExtensions.cs | 56 ++- .../Extensions/StringExtensions.cs | 20 +- 46 files changed, 1228 insertions(+), 1058 deletions(-) delete mode 100644 src/TypeCache/Data/DatabaseObject.cs delete mode 100644 src/TypeCache/Extensions/RulesBuilderExtensions.cs delete mode 100644 src/TypeCache/Mediation/RulesBuilder.cs delete mode 100644 src/TypeCache/Utilities/RegexCache.cs diff --git a/src/TypeCache.GraphQL/Extensions/GraphQLExtensions.cs b/src/TypeCache.GraphQL/Extensions/GraphQLExtensions.cs index 221589cd..e36433af 100644 --- a/src/TypeCache.GraphQL/Extensions/GraphQLExtensions.cs +++ b/src/TypeCache.GraphQL/Extensions/GraphQLExtensions.cs @@ -16,7 +16,6 @@ using TypeCache.Extensions; using TypeCache.GraphQL.Data; using TypeCache.GraphQL.Resolvers; -using TypeCache.GraphQL.SqlApi; using TypeCache.GraphQL.Types; using static System.FormattableString; using static System.Runtime.CompilerServices.MethodImplOptions; @@ -25,25 +24,90 @@ namespace TypeCache.GraphQL.Extensions; public static class GraphQLExtensions { + public static FieldType AddField(this IComplexGraphType @this, string name, IFieldResolver resolver) + => @this.AddField(new() + { + Name = name, + Type = typeof(T).ToGraphQLType(false), + Resolver = resolver + }); + + public static FieldType AddField(this IComplexGraphType @this, string name, IGraphType resolvedType, IFieldResolver resolver) + => @this.AddField(new() + { + Name = name, + ResolvedType = resolvedType, + Resolver = resolver + }); + public static FieldType AddField(this IComplexGraphType @this, MethodInfo methodInfo, IFieldResolver resolver) - { - var fieldType = methodInfo.ToFieldType(); - fieldType.Resolver = resolver; - return @this.AddField(fieldType); - } + => @this.AddField(new() + { + Arguments = new QueryArguments(methodInfo.GetParameters() + .Where(parameterInfo => !parameterInfo.GraphQLIgnore() && !parameterInfo.ParameterType.Is()) + .Select(parameterInfo => new QueryArgument(parameterInfo.ToGraphQLType()) + { + Name = parameterInfo.GraphQLName(), + Description = parameterInfo.GraphQLDescription(), + })), + Name = methodInfo.GraphQLName(), + Description = methodInfo.GraphQLDescription(), + DeprecationReason = methodInfo.GraphQLDeprecationReason(), + Resolver = resolver, + Type = methodInfo.ReturnType.ToGraphQLType(false).ToNonNullGraphType() + }); public static FieldType AddField(this IComplexGraphType @this, MethodInfo methodInfo, ISourceStreamResolver resolver) - { - var fieldType = methodInfo.ToFieldType(); - fieldType.StreamResolver = resolver; - return @this.AddField(fieldType); - } + => @this.AddField(new() + { + Arguments = new QueryArguments(methodInfo.GetParameters() + .Where(parameterInfo => !parameterInfo.GraphQLIgnore() && !parameterInfo.ParameterType.Is()) + .Select(parameterInfo => new QueryArgument(parameterInfo.ToGraphQLType()) + { + Name = parameterInfo.GraphQLName(), + Description = parameterInfo.GraphQLDescription(), + })), + Name = methodInfo.GraphQLName(), + Description = methodInfo.GraphQLDescription(), + DeprecationReason = methodInfo.GraphQLDeprecationReason(), + StreamResolver = resolver, + Type = methodInfo.ReturnType.ToGraphQLType(false).ToNonNullGraphType() + }); public static FieldType AddField(this IComplexGraphType @this, PropertyInfo propertyInfo, IFieldResolver resolver) { - var fieldType = propertyInfo.ToFieldType(); - fieldType.Resolver = resolver; - return @this.AddField(fieldType); + var type = propertyInfo.ToGraphQLType(false); + var arguments = new QueryArguments(); + + if (type.IsAssignableTo() && !type.Implements(typeof(NonNullGraphType<>))) + arguments.Add("null", type, description: "Return this if the value is null."); + + if (propertyInfo.PropertyType.IsAssignableTo()) + arguments.Add("format", nullable: true, description: "Use .NET format specifiers to format the data."); + + if (type.Is>() || type.Is>>()) + arguments.Add("timeZone", nullable: true, description: Invariant($"{typeof(TimeZoneInfo).Namespace}.{nameof(TimeZoneInfo)}.{nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId)}(value, [..., ...] | [UTC, ...])")); + else if (type.Is>() || type.Is>>()) + arguments.Add("timeZone", nullable: true, description: Invariant($"{typeof(TimeZoneInfo).Namespace}.{nameof(TimeZoneInfo)}.{nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId)}(value, ...)")); + else if (type.Is>() || type.Is>>()) + { + arguments.Add("case", description: "value.ToLower(), value.ToLowerInvariant(), value.ToUpper(), value.ToUpperInvariant()"); + arguments.Add("length", description: "value.Left(length)"); + arguments.Add("match", nullable: true, description: "value.ToRegex(RegexOptions.Compiled | RegexOptions.Singleline).Match(match)."); + arguments.Add("trim", nullable: true, description: "value.Trim()"); + arguments.Add("trimEnd", nullable: true, description: "value.TrimEnd()"); + arguments.Add("trimStart", nullable: true, description: "value.TrimStart()"); + } + + return @this.AddField(new() + { + Arguments = arguments, + Type = type, + Name = propertyInfo.GraphQLName(), + Description = propertyInfo.GraphQLDescription(), + DeprecationReason = propertyInfo.GraphQLDeprecationReason(), + Resolver = resolver + }); } public static void AddOrderBy(this EnumerationGraphType @this, string column, string? deprecationReason = null) @@ -175,64 +239,4 @@ public static Type ToListGraphType(this Type @this) [MethodImpl(AggressiveInlining), DebuggerHidden] public static Type ToNonNullGraphType(this Type @this) => typeof(NonNullGraphType<>).MakeGenericType(@this); - - public static FieldType ToFieldType(this MethodInfo @this) - => new() - { - Arguments = new QueryArguments(@this.GetParameters() - .Where(parameterInfo => !parameterInfo.GraphQLIgnore() && !parameterInfo.ParameterType.Is()) - .Select(parameterInfo => new QueryArgument(parameterInfo.ToGraphQLType()) - { - Name = parameterInfo.GraphQLName(), - Description = parameterInfo.GraphQLDescription(), - })), - Name = @this.GraphQLName(), - Description = @this.GraphQLDescription(), - DeprecationReason = @this.GraphQLDeprecationReason(), - Type = @this.ReturnType.ToGraphQLType(false).ToNonNullGraphType() - }; - - public static FieldType ToFieldType(this PropertyInfo @this) - { - var type = @this.ToGraphQLType(false); - var arguments = new QueryArguments(); - - if (type.IsAssignableTo() && !type.Implements(typeof(NonNullGraphType<>))) - arguments.Add("null", type, description: "Return this value instead of null."); - - if (@this.PropertyType.IsAssignableTo()) - arguments.Add("format", nullable: true, description: "Use .NET format specifiers to format the data."); - - if (type.Is>() || type.Is>>()) - arguments.Add("timeZone", nullable: true, description: Invariant($"{typeof(TimeZoneInfo).Namespace}.{nameof(TimeZoneInfo)}.{nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId)}(value, [..., ...] | [UTC, ...])")); - else if (type.Is>() || type.Is>>()) - arguments.Add("timeZone", nullable: true, description: Invariant($"{typeof(TimeZoneInfo).Namespace}.{nameof(TimeZoneInfo)}.{nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId)}(value, ...)")); - else if (type.Is>() || type.Is>>()) - { - arguments.Add("case", nullable: true, description: "Convert string value to upper or lower case."); - arguments.Add("length", nullable: true, description: "Exclude the rest of the string value if it exceeds this length."); - arguments.Add("match", nullable: true, description: "Returns the matching result based on the specified regular expression pattern, null if no match."); - arguments.Add("trim", nullable: true, description: Invariant($"{typeof(string).Namespace}.{nameof(String)}.{nameof(string.Trim)}(value)")); - arguments.Add("trimEnd", nullable: true, description: Invariant($"{typeof(string).Namespace}.{nameof(String)}.{nameof(string.TrimEnd)}(value)")); - arguments.Add("trimStart", nullable: true, description: Invariant($"{typeof(string).Namespace}.{nameof(String)}.{nameof(string.TrimStart)}(value)")); - } - - return new() - { - Arguments = arguments, - Type = type, - Name = @this.GraphQLName(), - Description = @this.GraphQLDescription(), - DeprecationReason = @this.GraphQLDeprecationReason(), - }; - } - - public static FieldType ToInputFieldType(this PropertyInfo @this) - => new() - { - Type = @this.ToGraphQLType(true), - Name = @this.GraphQLName(), - Description = @this.GraphQLDescription(), - DeprecationReason = @this.GraphQLDeprecationReason() - }; } diff --git a/src/TypeCache.GraphQL/Extensions/QueryArgumentsExtensions.cs b/src/TypeCache.GraphQL/Extensions/QueryArgumentsExtensions.cs index c7cbcb50..dd18ddab 100644 --- a/src/TypeCache.GraphQL/Extensions/QueryArgumentsExtensions.cs +++ b/src/TypeCache.GraphQL/Extensions/QueryArgumentsExtensions.cs @@ -10,7 +10,7 @@ namespace TypeCache.GraphQL.Extensions; public static class QueryArgumentsExtensions { - public static void Add(this QueryArguments @this, string name, bool nullable = false, object? defaultValue = null, string? description = null) + public static void Add(this QueryArguments @this, string name, bool? nullable = null, object? defaultValue = null, string? description = null) { if (typeof(T).Implements(typeof(IGraphType))) { @@ -31,6 +31,10 @@ public static void Add(this QueryArguments @this, string name, bool nullable _ => typeof(T) }; + var isValueNullable = type.Is(typeof(Nullable<>)); + if (isValueNullable) + type = type.GenericTypeArguments[0]; + var graphType = type switch { { IsEnum: true } => typeof(GraphQLEnumType<>), @@ -41,10 +45,22 @@ _ when type.Implements(typeof(ISpanParsable<>)) => typeof(GraphQLScalarType<>), type = graphType.MakeGenericType(type); if (isList) + { + if (!isValueNullable) + type = type.ToNonNullGraphType(); + type = type.ToListGraphType(); + nullable ??= defaultValue is not null; - if (!nullable) - type = type.ToNonNullGraphType(); + if (nullable is false) + type = type.ToNonNullGraphType(); + } + else + { + nullable ??= isValueNullable || defaultValue is not null; + if (nullable is false) + type = type.ToNonNullGraphType(); + } @this.Add(new QueryArgument(type) { diff --git a/src/TypeCache.GraphQL/Extensions/SchemaExtensions.cs b/src/TypeCache.GraphQL/Extensions/SchemaExtensions.cs index 96b0d8ef..eb6a735a 100644 --- a/src/TypeCache.GraphQL/Extensions/SchemaExtensions.cs +++ b/src/TypeCache.GraphQL/Extensions/SchemaExtensions.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Data; using System.Linq; -using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using GraphQL; @@ -220,7 +219,7 @@ public static void AddDatabaseEndpoints(this ISchema @this, IDataSource dataSour var arguments = new QueryArguments(); arguments.Add("parameters", nullable: true, description: "Used to reference user input values from the where clause."); if (dataSource.Type is DataSourceType.SqlServer) - arguments.Add(nameof(SelectQuery.Top)); + arguments.Add(nameof(SelectQuery.Top), nullable: true, description: "Accepts integer `n` or `n%`."); arguments.Add(nameof(SelectQuery.Distinct), defaultValue: false); arguments.Add(nameof(SelectQuery.Where), nullable: true, description: "If `where` is omitted, all records will be returned."); @@ -308,7 +307,7 @@ public static void AddDatabaseEndpoints(this ISchema @this, IDataSource dataSour var arguments = new QueryArguments(); arguments.Add("parameters", nullable: true, description: "Used to reference user input values from the where clause."); if (dataSource.Type is DataSourceType.SqlServer) - arguments.Add>(nameof(SelectQuery.Top)); + arguments.Add(nameof(SelectQuery.Top), nullable: true, description: "Accepts integer `n` or `n%`."); arguments.Add(nameof(SelectQuery.Distinct), defaultValue: false); arguments.Add(nameof(SelectQuery.Where), nullable: true, description: "If `where` is omitted, all records will be returned."); @@ -840,7 +839,7 @@ public static FieldType AddSqlApiCallProcedureEndpoint(this ISchema @this, ID dataSource.AssertNotNull(); procedure.AssertNotBlank(); - var name = dataSource.CreateName(procedure); + var name = dataSource.Escape(procedure); var objectSchema = dataSource.ObjectSchemas[name]; var parameters = objectSchema.Parameters .Where(_ => _.Direction is ParameterDirection.Input || _.Direction is ParameterDirection.InputOutput) @@ -885,7 +884,7 @@ public static FieldType AddSqlApiDeleteDataEndpoint(this ISchema @this, IData dataSource.AssertNotNull(); table.AssertNotBlank(); - var name = dataSource.CreateName(table); + var name = dataSource.Escape(table); var objectSchema = dataSource.ObjectSchemas[name]; var arguments = new QueryArguments(); arguments.Add("data", description: "The data to be deleted."); @@ -920,7 +919,7 @@ public static FieldType AddSqlApiDeleteEndpoint(this ISchema @this, IDataSour dataSource.AssertNotNull(); table.AssertNotBlank(); - var name = dataSource.CreateName(table); + var name = dataSource.Escape(table); var objectSchema = dataSource.ObjectSchemas[name]; var arguments = new QueryArguments(); arguments.Add("parameters", nullable: true, description: "Used to reference user input values from the where clause."); @@ -956,7 +955,7 @@ public static FieldType AddSqlApiInsertDataEndpoint(this ISchema @this, IData dataSource.AssertNotNull(); table.AssertNotBlank(); - var name = dataSource.CreateName(table); + var name = dataSource.Escape(table); var objectSchema = dataSource.ObjectSchemas[name]; var arguments = new QueryArguments(); arguments.Add("columns", description: "The columns to insert data into."); @@ -992,7 +991,7 @@ public static FieldType AddSqlApiInsertEndpoint(this ISchema @this, IDataSour dataSource.AssertNotNull(); table.AssertNotBlank(); - var name = dataSource.CreateName(table); + var name = dataSource.Escape(table); var objectSchema = dataSource.ObjectSchemas[name]; var graphOrderByEnum = new EnumerationGraphType { @@ -1011,7 +1010,7 @@ public static FieldType AddSqlApiInsertEndpoint(this ISchema @this, IDataSour var arguments = new QueryArguments(); arguments.Add("parameters", nullable: true, description: "Used to reference user input values from the where clause."); if (dataSource.Type is DataSourceType.SqlServer) - arguments.Add(nameof(SelectQuery.Top), nullable: true); + arguments.Add(nameof(SelectQuery.Top), nullable: true, description: "Accepts integer `n` or `n%`."); arguments.Add(nameof(SelectQuery.Distinct), defaultValue: false); arguments.Add(nameof(SelectQuery.From), description: "The table or view to pull the data from to insert."); @@ -1050,7 +1049,7 @@ public static FieldType AddSqlApiSelectEndpoint(this ISchema @this, IDataSour dataSource.AssertNotNull(); table.AssertNotBlank(); - var name = dataSource.CreateName(table); + var name = dataSource.Escape(table); var objectSchema = dataSource.ObjectSchemas[name]; var graphOrderByEnum = new EnumerationGraphType { @@ -1067,7 +1066,7 @@ public static FieldType AddSqlApiSelectEndpoint(this ISchema @this, IDataSour var arguments = new QueryArguments(); arguments.Add("parameters", nullable: true, description: "Used to reference user input values from the where clause."); if (dataSource.Type is DataSourceType.SqlServer) - arguments.Add(nameof(SelectQuery.Top), nullable: true); + arguments.Add(nameof(SelectQuery.Top), nullable: true, description: "Accepts integer `n` or `n%`."); arguments.Add(nameof(SelectQuery.Distinct), defaultValue: false); arguments.Add(nameof(SelectQuery.Where), nullable: true, description: "If `where` is omitted, all records will be returned."); @@ -1105,7 +1104,7 @@ public static FieldType AddSqlApiUpdateDataEndpoint(this ISchema @this, IData dataSource.AssertNotNull(); table.AssertNotBlank(); - var name = dataSource.CreateName(table); + var name = dataSource.Escape(table); var objectSchema = dataSource.ObjectSchemas[name]; var arguments = new QueryArguments(); arguments.Add("set", description: "The columns to be updated."); @@ -1140,7 +1139,7 @@ public static FieldType AddSqlApiUpdateEndpoint(this ISchema @this, IDataSour dataSource.AssertNotNull(); table.AssertNotBlank(); - var name = dataSource.CreateName(table); + var name = dataSource.Escape(table); var objectSchema = dataSource.ObjectSchemas[name]; var arguments = new QueryArguments(); arguments.Add("parameters", nullable: true, description: "Used to reference user input values from the where clause."); diff --git a/src/TypeCache.GraphQL/Resolvers/FieldResolver.cs b/src/TypeCache.GraphQL/Resolvers/FieldResolver.cs index 0658ec70..ff35244d 100644 --- a/src/TypeCache.GraphQL/Resolvers/FieldResolver.cs +++ b/src/TypeCache.GraphQL/Resolvers/FieldResolver.cs @@ -16,9 +16,15 @@ public abstract class FieldResolver : IFieldResolver { return await this.ResolveAsync(context); } + catch (ExecutionError error) + { + context.Errors.Add(error); + return null; + } catch (AggregateException error) { - var executionErrors = error.InnerExceptions.Select(exception => new ExecutionError(exception.Message, exception)); + var executionErrors = error.InnerExceptions.Select(exception => + exception as ExecutionError ?? new ExecutionError(exception.Message, exception)); context.Errors.AddRange(executionErrors); return null; } diff --git a/src/TypeCache.GraphQL/Resolvers/PropertyFieldResolver.cs b/src/TypeCache.GraphQL/Resolvers/PropertyFieldResolver.cs index 544da892..622ed85d 100644 --- a/src/TypeCache.GraphQL/Resolvers/PropertyFieldResolver.cs +++ b/src/TypeCache.GraphQL/Resolvers/PropertyFieldResolver.cs @@ -8,21 +8,15 @@ using TypeCache.Extensions; using static System.FormattableString; using static System.Globalization.CultureInfo; -using static System.Text.RegularExpressions.RegexOptions; namespace TypeCache.GraphQL.Resolvers; -public sealed class PropertyFieldResolver : FieldResolver +public sealed class PropertyFieldResolver(PropertyInfo propertyInfo) : FieldResolver { - private readonly PropertyInfo _PropertyInfo; - - public PropertyFieldResolver(PropertyInfo propertyInfo) - { - this._PropertyInfo = propertyInfo; - } - protected override async ValueTask ResolveAsync(IResolveFieldContext context) { + propertyInfo.AssertNotNull(); + var source = context.Source switch { null => null, @@ -31,7 +25,7 @@ public PropertyFieldResolver(PropertyInfo propertyInfo) _ => context.Source }; - var value = this._PropertyInfo.GetPropertyValue(source!); + var value = propertyInfo.GetPropertyValue(source!); if (value is null) return value ?? context.GetArgument("null"); @@ -87,7 +81,7 @@ public PropertyFieldResolver(PropertyInfo propertyInfo) var pattern = context.GetArgument("match"); if (pattern.IsNotBlank()) { - var match = Regex.Match(text, pattern, Compiled | Singleline); + var match = text.ToRegex(RegexOptions.Compiled | RegexOptions.Singleline).Match(text); if (match.Success) text = match.Value; else diff --git a/src/TypeCache.GraphQL/Resolvers/SqlApiInsertFieldResolver.cs b/src/TypeCache.GraphQL/Resolvers/SqlApiInsertFieldResolver.cs index c36801c9..ef6a3817 100644 --- a/src/TypeCache.GraphQL/Resolvers/SqlApiInsertFieldResolver.cs +++ b/src/TypeCache.GraphQL/Resolvers/SqlApiInsertFieldResolver.cs @@ -36,12 +36,17 @@ public sealed class SqlApiInsertFieldResolver : FieldResolver .ToArray(); var columns = context.GetArgument("columns"); var data = context.GetArgumentAsDataTable("data", objectSchema); - var sql = data.Rows.Any() - ? objectSchema.CreateInsertSQL(data, output) - : objectSchema.CreateInsertSQL(columns, new SelectQuery + + string sql; + if (data.Rows.Any()) + sql = objectSchema.CreateInsertSQL(data, output); + else + { + var top = context.GetArgument(nameof(SelectQuery.Top)); + sql = objectSchema.CreateInsertSQL(columns, new SelectQuery { Distinct = context.GetArgument(nameof(SelectQuery.Distinct)), - From = objectSchema.DataSource.CreateName(context.GetArgument(nameof(SelectQuery.From))), + From = objectSchema.DataSource.Escape(context.GetArgument(nameof(SelectQuery.From))), Fetch = context.GetArgument(nameof(SelectQuery.Fetch)), Offset = context.GetArgument(nameof(SelectQuery.Offset)), OrderBy = context.GetArgument(nameof(SelectQuery.OrderBy)), @@ -50,11 +55,13 @@ public sealed class SqlApiInsertFieldResolver : FieldResolver .Select(column => column.Name) .ToArray(), TableHints = objectSchema.DataSource.Type is SqlServer ? "NOLOCK" : null, - Top = context.GetArgument(nameof(SelectQuery.Top)), + Top = top.IsNotBlank() ? top.TrimEnd('%').Parse() : null, + TopPercent = top?.EndsWith('%') is true, Where = context.GetArgument(nameof(SelectQuery.Where)) }, output); - var sqlCommand = objectSchema.DataSource.CreateSqlCommand(sql); + } + var sqlCommand = objectSchema.DataSource.CreateSqlCommand(sql); context.GetArgument("parameters")?.ForEach(parameter => sqlCommand.Parameters[parameter.Name] = parameter.Value); var result = Array.Empty; @@ -93,12 +100,17 @@ public sealed class SqlApiInsertFieldResolver : FieldResolver .ToArray(); var columns = context.GetArgument("columns"); var data = context.GetArgument("data"); - var sql = data.Any() - ? objectSchema.CreateInsertSQL(columns, data, output) - : objectSchema.CreateInsertSQL(columns, new SelectQuery + + string sql; + if (data.Any()) + sql = objectSchema.CreateInsertSQL(columns, data, output); + else + { + var top = context.GetArgument(nameof(SelectQuery.Top)); + sql = objectSchema.CreateInsertSQL(columns, new SelectQuery { Distinct = context.GetArgument(nameof(SelectQuery.Distinct)), - From = objectSchema.DataSource.CreateName(context.GetArgument(nameof(SelectQuery.From))), + From = objectSchema.DataSource.Escape(context.GetArgument(nameof(SelectQuery.From))), Fetch = context.GetArgument(nameof(SelectQuery.Fetch)), Offset = context.GetArgument(nameof(SelectQuery.Offset)), OrderBy = context.GetArgument(nameof(SelectQuery.OrderBy)), @@ -107,11 +119,13 @@ public sealed class SqlApiInsertFieldResolver : FieldResolver .Select(column => column.Name) .ToArray(), TableHints = objectSchema.DataSource.Type is SqlServer ? "NOLOCK" : null, - Top = context.GetArgument(nameof(SelectQuery.Top)), + Top = top.IsNotBlank() ? top.TrimEnd('%').Parse() : null, + TopPercent = top?.EndsWith('%') is true, Where = context.GetArgument(nameof(SelectQuery.Where)) }, output); - var sqlCommand = objectSchema.DataSource.CreateSqlCommand(sql); + } + var sqlCommand = objectSchema.DataSource.CreateSqlCommand(sql); context.GetArgument("parameters")?.ForEach(parameter => sqlCommand.Parameters[parameter.Name] = parameter.Value); var result = (IList)Array.Empty; diff --git a/src/TypeCache.GraphQL/Resolvers/SqlApiSelectFieldResolver.cs b/src/TypeCache.GraphQL/Resolvers/SqlApiSelectFieldResolver.cs index 5388830e..9facb696 100644 --- a/src/TypeCache.GraphQL/Resolvers/SqlApiSelectFieldResolver.cs +++ b/src/TypeCache.GraphQL/Resolvers/SqlApiSelectFieldResolver.cs @@ -24,6 +24,7 @@ public sealed class SqlApiSelectFieldResolver : FieldResolver var mediator = context.RequestServices!.GetRequiredService(); var objectSchema = context.FieldDefinition.GetMetadata(nameof(ObjectSchema)); var selections = context.GetSelections().ToArray(); + var top = context.GetArgument(nameof(SelectQuery.Top)); var select = new SelectQuery { Distinct = context.GetArgument(nameof(SelectQuery.Distinct)), @@ -37,7 +38,8 @@ public sealed class SqlApiSelectFieldResolver : FieldResolver .Select(column => column.Name) .ToArray(), TableHints = objectSchema.DataSource.Type is SqlServer ? "NOLOCK" : null, - Top = context.GetArgument(nameof(SelectQuery.Top)).ToString(), + Top = top.IsNotBlank() ? top.TrimEnd('%').Parse() : null, + TopPercent = top?.EndsWith('%') is true, Where = context.GetArgument(nameof(SelectQuery.Where)) }; var sql = objectSchema.CreateSelectSQL(select); @@ -100,6 +102,7 @@ public sealed class SqlApiSelectFieldResolver : FieldResolver var mediator = context.RequestServices!.GetRequiredService(); var objectSchema = context.FieldDefinition.GetMetadata(nameof(ObjectSchema)); var selections = context.GetSelections().ToArray(); + var top = context.GetArgument(nameof(SelectQuery.Top)); var select = new SelectQuery { Distinct = context.GetArgument(nameof(SelectQuery.Distinct)), @@ -113,7 +116,8 @@ public sealed class SqlApiSelectFieldResolver : FieldResolver .Select(column => column.Name) .ToArray(), TableHints = objectSchema.DataSource.Type is SqlServer ? "NOLOCK" : null, - Top = context.GetArgument(nameof(SelectQuery.Top)).ToString(), + Top = top.IsNotBlank() ? top.TrimEnd('%').Parse() : null, + TopPercent = top?.EndsWith('%') is true, Where = context.GetArgument(nameof(SelectQuery.Where)) }; var sql = objectSchema.CreateSelectSQL(select); diff --git a/src/TypeCache.GraphQL/SqlApi/DataResponse.cs b/src/TypeCache.GraphQL/SqlApi/DataResponse.cs index 453fbad8..80d0cfa3 100644 --- a/src/TypeCache.GraphQL/SqlApi/DataResponse.cs +++ b/src/TypeCache.GraphQL/SqlApi/DataResponse.cs @@ -9,7 +9,6 @@ using TypeCache.Extensions; using TypeCache.GraphQL.Attributes; using TypeCache.GraphQL.Extensions; -using TypeCache.GraphQL.Types; using static System.FormattableString; namespace TypeCache.GraphQL.SqlApi; @@ -63,48 +62,13 @@ public static ObjectGraphType CreateGraphType(string name, string description, I }; var edgeType = CreateEdgeGraphType(name, description, dataGraphType); - graphType.AddField(new() - { - Name = nameof(DataResponse.DataSource), - Type = typeof(GraphQLScalarType), - Resolver = new FuncFieldResolver(context => context.Source.DataSource) - }); - graphType.AddField(new() - { - Name = nameof(DataResponse.Edges), - ResolvedType = new ListGraphType(new NonNullGraphType(edgeType)), - Resolver = new FuncFieldResolver[]?>(context => context.Source.Edges) - }); - graphType.AddField(new() - { - Name = nameof(DataResponse.Items), - ResolvedType = new ListGraphType(new NonNullGraphType(dataGraphType)), - Resolver = new FuncFieldResolver?>(context => context.Source.Items!) - }); - graphType.AddField(new() - { - Name = nameof(DataResponse.PageInfo), - Type = typeof(GraphQLObjectType), - Resolver = new FuncFieldResolver(context => context.Source.PageInfo) - }); - graphType.AddField(new() - { - Name = nameof(DataResponse.Sql), - Type = typeof(GraphQLScalarType), - Resolver = new FuncFieldResolver(context => context.Source.Sql) - }); - graphType.AddField(new() - { - Name = nameof(DataResponse.Table), - Type = typeof(GraphQLScalarType), - Resolver = new FuncFieldResolver(context => context.Source.Table) - }); - graphType.AddField(new() - { - Name = nameof(DataResponse.TotalCount), - Type = ScalarType.Int32.ToGraphType(), - Resolver = new FuncFieldResolver(context => context.Source.TotalCount) - }); + graphType.AddField(nameof(DataResponse.DataSource), new FuncFieldResolver(context => context.Source.DataSource)); + graphType.AddField(nameof(DataResponse.Edges), new ListGraphType(new NonNullGraphType(edgeType)), new FuncFieldResolver[]?>(context => context.Source.Edges)); + graphType.AddField(nameof(DataResponse.Items), new ListGraphType(new NonNullGraphType(dataGraphType)), new FuncFieldResolver?>(context => context.Source.Items!)); + graphType.AddField(nameof(DataResponse.PageInfo), new FuncFieldResolver(context => context.Source.PageInfo)); + graphType.AddField(nameof(DataResponse.Sql), new FuncFieldResolver(context => context.Source.Sql)); + graphType.AddField(nameof(DataResponse.Table), new FuncFieldResolver(context => context.Source.Table)); + graphType.AddField(nameof(DataResponse.TotalCount), new FuncFieldResolver(context => context.Source.TotalCount)); return graphType; } @@ -116,18 +80,8 @@ public static ObjectGraphType CreateEdgeGraphType(string name, string descriptio Name = Invariant($"{name}Edge"), Description = description }; - graphType.AddField(new() - { - Name = nameof(Edge.Cursor), - Type = typeof(GraphQLScalarType), - Resolver = new FuncFieldResolver, string>(context => context.Source.Cursor) - }); - graphType.AddField(new() - { - Name = nameof(Edge.Node), - ResolvedType = new NonNullGraphType(dataGraphType), - Resolver = new FuncFieldResolver, DataRow>(context => context.Source.Node) - }); + graphType.AddField(nameof(Edge.Cursor), new FuncFieldResolver, string>(context => context.Source.Cursor)); + graphType.AddField(nameof(Edge.Node), new NonNullGraphType(dataGraphType), new FuncFieldResolver, DataRow>(context => context.Source.Node)); return graphType; } } diff --git a/src/TypeCache.GraphQL/SqlApi/OutputResponse.cs b/src/TypeCache.GraphQL/SqlApi/OutputResponse.cs index 1754707b..a2a81b51 100644 --- a/src/TypeCache.GraphQL/SqlApi/OutputResponse.cs +++ b/src/TypeCache.GraphQL/SqlApi/OutputResponse.cs @@ -4,7 +4,7 @@ using GraphQL.Resolvers; using GraphQL.Types; using TypeCache.GraphQL.Attributes; -using TypeCache.GraphQL.Types; +using TypeCache.GraphQL.Extensions; using static System.FormattableString; namespace TypeCache.GraphQL.SqlApi; @@ -30,36 +30,11 @@ public static ObjectGraphType CreateGraphType(string name, string description, I Description = description }; - graphType.AddField(new() - { - Name = nameof(OutputResponse.DataSource), - Type = typeof(GraphQLScalarType), - Resolver = new FuncFieldResolver, string?>(context => context.Source.DataSource) - }); - graphType.AddField(new() - { - Name = nameof(OutputResponse.Output), - ResolvedType = new ListGraphType(new NonNullGraphType(dataGraphType)), - Resolver = new FuncFieldResolver, IList?>(context => context.Source.Output) - }); - graphType.AddField(new() - { - Name = nameof(OutputResponse.Sql), - Type = typeof(GraphQLScalarType), - Resolver = new FuncFieldResolver, string?>(context => context.Source.Sql) - }); - graphType.AddField(new() - { - Name = nameof(OutputResponse.Table), - Type = typeof(GraphQLScalarType), - Resolver = new FuncFieldResolver, string?>(context => context.Source.Table) - }); - graphType.AddField(new() - { - Name = nameof(OutputResponse.TotalCount), - Type = typeof(LongGraphType), - Resolver = new FuncFieldResolver, long?>(context => context.Source.TotalCount) - }); + graphType.AddField(nameof(OutputResponse.DataSource), new FuncFieldResolver, string?>(context => context.Source.DataSource)); + graphType.AddField(nameof(OutputResponse.Output), new ListGraphType(new NonNullGraphType(dataGraphType)), new FuncFieldResolver, IList?>(context => context.Source.Output)); + graphType.AddField(nameof(OutputResponse.Sql), new FuncFieldResolver, string?>(context => context.Source.Sql)); + graphType.AddField(nameof(OutputResponse.Table), new FuncFieldResolver, string?>(context => context.Source.Table)); + graphType.AddField(nameof(OutputResponse.TotalCount), new FuncFieldResolver, long?>(context => context.Source.TotalCount)); return graphType; } diff --git a/src/TypeCache.GraphQL/SqlApi/SelectResponse.cs b/src/TypeCache.GraphQL/SqlApi/SelectResponse.cs index a9f4de18..3a4ce671 100644 --- a/src/TypeCache.GraphQL/SqlApi/SelectResponse.cs +++ b/src/TypeCache.GraphQL/SqlApi/SelectResponse.cs @@ -4,7 +4,7 @@ using GraphQL.Types; using TypeCache.GraphQL.Attributes; using TypeCache.GraphQL.Data; -using TypeCache.GraphQL.Types; +using TypeCache.GraphQL.Extensions; using static System.FormattableString; namespace TypeCache.GraphQL.SqlApi; @@ -43,30 +43,10 @@ public static ObjectGraphType CreateGraphType(string name, string description, I Description = description }; - graphType.AddField(new() - { - Name = nameof(SelectResponse.Data), - ResolvedType = Connection.CreateGraphType(name, dataGraphType), - Resolver = new FuncFieldResolver, Connection>(context => context.Source.Data) - }); - graphType.AddField(new() - { - Name = nameof(SelectResponse.DataSource), - Type = typeof(GraphQLScalarType), - Resolver = new FuncFieldResolver, string>(context => context.Source.DataSource) - }); - graphType.AddField(new() - { - Name = nameof(SelectResponse.Sql), - Type = typeof(GraphQLScalarType), - Resolver = new FuncFieldResolver, string>(context => context.Source.Sql) - }); - graphType.AddField(new() - { - Name = nameof(SelectResponse.Table), - Type = typeof(GraphQLScalarType), - Resolver = new FuncFieldResolver, string>(context => context.Source.Table) - }); + graphType.AddField(nameof(SelectResponse.Data), Connection.CreateGraphType(name, dataGraphType), new FuncFieldResolver, Connection>(context => context.Source.Data)); + graphType.AddField(nameof(SelectResponse.DataSource), new FuncFieldResolver, string>(context => context.Source.DataSource)); + graphType.AddField(nameof(SelectResponse.Sql), new FuncFieldResolver, string>(context => context.Source.Sql)); + graphType.AddField(nameof(SelectResponse.Table), new FuncFieldResolver, string>(context => context.Source.Table)); return graphType; } diff --git a/src/TypeCache.GraphQL/TypeCache.GraphQL.csproj b/src/TypeCache.GraphQL/TypeCache.GraphQL.csproj index 2b59dd8d..f2bdcbc7 100644 --- a/src/TypeCache.GraphQL/TypeCache.GraphQL.csproj +++ b/src/TypeCache.GraphQL/TypeCache.GraphQL.csproj @@ -4,7 +4,7 @@ enable TypeCache.GraphQL TypeCache.GraphQL - 8.1.4 + 8.2.0 Samuel Abraham <sam987883@gmail.com> Samuel Abraham <sam987883@gmail.com> TypeCache GraphQL diff --git a/src/TypeCache.GraphQL/Types/GraphQLInputType.cs b/src/TypeCache.GraphQL/Types/GraphQLInputType.cs index b2e4dd97..e80ae321 100644 --- a/src/TypeCache.GraphQL/Types/GraphQLInputType.cs +++ b/src/TypeCache.GraphQL/Types/GraphQLInputType.cs @@ -19,6 +19,12 @@ public GraphQLInputType() typeof(T).GetPublicProperties() .Where(propertyInfo => propertyInfo.CanRead && propertyInfo.CanWrite && !propertyInfo.GraphQLIgnore()) .ToArray() - .ForEach(propertyInfo => this.AddField(propertyInfo.ToInputFieldType())); + .ForEach(propertyInfo => this.AddField(new() + { + Type = propertyInfo.ToGraphQLType(true), + Name = propertyInfo.GraphQLName(), + Description = propertyInfo.GraphQLDescription(), + DeprecationReason = propertyInfo.GraphQLDeprecationReason() + })); } } diff --git a/src/TypeCache.Web/Handlers/SqlApiHandler.cs b/src/TypeCache.Web/Handlers/SqlApiHandler.cs index cb85e186..12abd535 100644 --- a/src/TypeCache.Web/Handlers/SqlApiHandler.cs +++ b/src/TypeCache.Web/Handlers/SqlApiHandler.cs @@ -11,6 +11,7 @@ using TypeCache.Extensions; using TypeCache.Mediation; using static System.FormattableString; +using static System.StringSplitOptions; using static System.Net.Mime.MediaTypeNames; using static System.Text.Encoding; @@ -129,7 +130,7 @@ HttpContext httpContext , [FromBody] SelectQuery selectQuery) { var objectSchema = httpContext.GetObjectSchema(); - var sql = objectSchema.CreateInsertSQL(columns.Split(','), selectQuery, [output]); + var sql = objectSchema.CreateInsertSQL(columns.Split(',', RemoveEmptyEntries | TrimEntries), selectQuery, [output]); var sqlCommand = objectSchema.DataSource.CreateSqlCommand(sql); foreach (var pair in httpContext.Request.Query.Where(pair => pair.Key.StartsWith('@'))) @@ -213,18 +214,18 @@ HttpContext httpContext , [FromRoute] string table , [FromQuery] string columns , [FromQuery] bool distinct - , [FromQuery] string distinctOn + , [FromQuery] string? distinctOn , [FromQuery] uint fetch - , [FromQuery] string groupBy + , [FromQuery] string? groupBy , [FromQuery] GroupBy groupByOption - , [FromQuery] string having + , [FromQuery] string? having , [FromQuery] uint offset - , [FromQuery] string orderBy + , [FromQuery] string? orderBy , [FromQuery] string output - , [FromQuery] string select - , [FromQuery] string tableHints - , [FromQuery] string top - , [FromQuery] string where) + , [FromQuery] string? select + , [FromQuery] string? tableHints + , [FromQuery] string? top + , [FromQuery] string? where) { var objectSchema = httpContext.GetObjectSchema(); @@ -234,7 +235,7 @@ HttpContext httpContext if (select.IsBlank()) return Results.BadRequest(Invariant($"[{nameof(select)}] query parameter must be specified.")); - if (columns.Split(',').Length != select.Split(',').Length) + if (columns.Split(',', RemoveEmptyEntries | TrimEntries).Length != select.Split(',', RemoveEmptyEntries | TrimEntries).Length) return Results.BadRequest(Invariant($"[{nameof(columns)}] and [{nameof(select)}] query parameters must have same number of columns/expressions.")); var selectQuery = new SelectQuery @@ -243,17 +244,18 @@ HttpContext httpContext DistinctOn = distinctOn, Fetch = fetch, From = objectSchema.Name, - GroupBy = groupBy?.Split(','), + GroupBy = groupBy?.Split(',', RemoveEmptyEntries | TrimEntries), GroupByOption = groupByOption, Having = having, Offset = offset, - OrderBy = orderBy?.Split(','), - Select = select?.Split(','), + OrderBy = orderBy?.Split(',', RemoveEmptyEntries | TrimEntries), + Select = select?.Split(',', RemoveEmptyEntries | TrimEntries), TableHints = tableHints, - Top = top, + Top = top.IsNotBlank() ? uint.Parse(top.TrimEnd('%')) : null, + TopPercent = top?.EndsWith('%') is true, Where = where }; - var sql = objectSchema.CreateInsertSQL(columns.Split(','), selectQuery, [output]); + var sql = objectSchema.CreateInsertSQL(columns.Split(',', RemoveEmptyEntries | TrimEntries), selectQuery, [output]); return Results.Text(sql, Text.Plain, UTF8); } @@ -292,17 +294,17 @@ HttpContext httpContext , [FromRoute] string schema , [FromRoute] string table , [FromQuery] bool distinct - , [FromQuery] string distinctOn + , [FromQuery] string? distinctOn , [FromQuery] uint fetch - , [FromQuery] string groupBy + , [FromQuery] string? groupBy , [FromQuery] GroupBy groupByOption - , [FromQuery] string having + , [FromQuery] string? having , [FromQuery] uint offset - , [FromQuery] string orderBy - , [FromQuery] string select - , [FromQuery] string tableHints - , [FromQuery] string top - , [FromQuery] string where) + , [FromQuery] string? orderBy + , [FromQuery] string? select + , [FromQuery] string? tableHints + , [FromQuery] string? top + , [FromQuery] string? where) { var objectSchema = httpContext.GetObjectSchema(); if (select.IsBlank()) @@ -314,14 +316,15 @@ HttpContext httpContext DistinctOn = distinctOn, Fetch = fetch, From = objectSchema.Name, - GroupBy = groupBy?.Split(','), + GroupBy = groupBy?.Split(',', RemoveEmptyEntries | TrimEntries), GroupByOption = groupByOption, Having = having, Offset = offset, - OrderBy = orderBy?.Split(','), - Select = select?.Split(','), + OrderBy = orderBy?.Split(',', RemoveEmptyEntries | TrimEntries), + Select = select?.Split(',', RemoveEmptyEntries | TrimEntries), TableHints = tableHints, - Top = top, + Top = top.IsNotBlank() ? uint.Parse(top.TrimEnd('%')) : null, + TopPercent = top?.EndsWith('%') is true, Where = where }; var sql = objectSchema.CreateSelectSQL(selectQuery); @@ -339,7 +342,7 @@ HttpContext httpContext , [FromQuery] string where) { var objectSchema = httpContext.GetObjectSchema(); - var sql = objectSchema.CreateUpdateSQL(set.Split(','), where, [output]); + var sql = objectSchema.CreateUpdateSQL(set.Split(',', RemoveEmptyEntries | TrimEntries), where, [output]); return Results.Text(sql, Text.Plain, UTF8); } @@ -364,17 +367,17 @@ HttpContext httpContext , [FromRoute] string schema , [FromRoute] string table , [FromQuery] bool distinct - , [FromQuery] string distinctOn + , [FromQuery] string? distinctOn , [FromQuery] uint fetch - , [FromQuery] string groupBy + , [FromQuery] string? groupBy , [FromQuery] GroupBy groupByOption - , [FromQuery] string having + , [FromQuery] string? having , [FromQuery] uint offset - , [FromQuery] string orderBy - , [FromQuery] string select - , [FromQuery] string tableHints - , [FromQuery] string top - , [FromQuery] string where) + , [FromQuery] string? orderBy + , [FromQuery] string? select + , [FromQuery] string? tableHints + , [FromQuery] string? top + , [FromQuery] string? where) { var objectSchema = httpContext.GetObjectSchema(); if (select.IsBlank()) @@ -386,14 +389,15 @@ HttpContext httpContext DistinctOn = distinctOn, Fetch = fetch, From = objectSchema.Name, - GroupBy = groupBy?.Split(','), + GroupBy = groupBy?.Split(',', RemoveEmptyEntries | TrimEntries), GroupByOption = groupByOption, Having = having, Offset = offset, - OrderBy = orderBy?.Split(','), - Select = select?.Split(','), + OrderBy = orderBy?.Split(',', RemoveEmptyEntries | TrimEntries), + Select = select?.Split(',', RemoveEmptyEntries | TrimEntries), TableHints = tableHints, - Top = top, + Top = top.IsNotBlank() ? uint.Parse(top.TrimEnd('%')) : null, + TopPercent = top?.EndsWith('%') is true, Where = where }; var sql = objectSchema.CreateSelectSQL(selectQuery); @@ -419,7 +423,7 @@ HttpContext httpContext , [FromQuery] string where) { var objectSchema = httpContext.GetObjectSchema(); - var sql = objectSchema.CreateUpdateSQL(set.Split(','), where, [output]); + var sql = objectSchema.CreateUpdateSQL(set.Split(',', RemoveEmptyEntries | TrimEntries), where, [output]); var sqlCommand = objectSchema.DataSource.CreateSqlCommand(sql); foreach (var pair in httpContext.Request.Query.Where(pair => pair.Key.StartsWith('@'))) diff --git a/src/TypeCache.Web/TypeCache.Web.csproj b/src/TypeCache.Web/TypeCache.Web.csproj index ef96ad48..bc910013 100644 --- a/src/TypeCache.Web/TypeCache.Web.csproj +++ b/src/TypeCache.Web/TypeCache.Web.csproj @@ -5,7 +5,7 @@ enable TypeCache.Web TypeCache.Web - 8.1.3 + 8.2.0 Samuel Abraham <sam987883@gmail.com> Samuel Abraham <sam987883@gmail.com> TypeCache Web Library diff --git a/src/TypeCache/Attributes/ServiceLifetimeAttribute.cs b/src/TypeCache/Attributes/ServiceLifetimeAttribute.cs index 5d3fe20b..45aa94f1 100644 --- a/src/TypeCache/Attributes/ServiceLifetimeAttribute.cs +++ b/src/TypeCache/Attributes/ServiceLifetimeAttribute.cs @@ -8,11 +8,13 @@ namespace TypeCache.Attributes; /// Used to automatically register types. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] -public class ServiceLifetimeAttribute(ServiceLifetime serviceLifetime, Type? serviceType = null) : Attribute +public class ServiceLifetimeAttribute(ServiceLifetime serviceLifetime, object? key, Type? serviceType = null) : Attribute { public ServiceLifetime ServiceLifetime { get; } = serviceLifetime; public Type? ServiceType { get; } = serviceType; + + public object? Key { get; } = key; } /// @@ -20,10 +22,6 @@ public class ServiceLifetimeAttribute(ServiceLifetime serviceLifetime, Type? ser /// /// The contract that this type implements, or specify the current type to register itself. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] -public sealed class ServiceLifetimeAttribute : ServiceLifetimeAttribute +public sealed class ServiceLifetimeAttribute(ServiceLifetime serviceLifetime, object? key = null) : ServiceLifetimeAttribute(serviceLifetime, key, typeof(T)) { - public ServiceLifetimeAttribute(ServiceLifetime serviceLifetime) - : base(serviceLifetime, typeof(T)) - { - } } diff --git a/src/TypeCache/Data/ColumnSchema.cs b/src/TypeCache/Data/ColumnSchema.cs index 25457c11..72fd2365 100644 --- a/src/TypeCache/Data/ColumnSchema.cs +++ b/src/TypeCache/Data/ColumnSchema.cs @@ -1,7 +1,29 @@ // Copyright (c) 2021 Samuel Abraham +using TypeCache.Extensions; + namespace TypeCache.Data; -public sealed record ColumnSchema(string Name, bool Nullable, bool PrimaryKey, bool ReadOnly, bool Unique, RuntimeTypeHandle DataTypeHandle) +public sealed class ColumnSchema(string name, bool nullable, bool primaryKey, bool readOnly, bool unique, RuntimeTypeHandle dataTypeHandle) : IEquatable { + public string Name { get; } = name; + + public bool Nullable { get; } = nullable; + + public bool PrimaryKey { get; } = primaryKey; + + public bool ReadOnly { get; } = readOnly; + + public bool Unique { get; } = unique; + + public RuntimeTypeHandle DataTypeHandle { get; } = dataTypeHandle; + + public bool Equals(ColumnSchema? other) + => this.Name.Is(other?.Name); + + public override bool Equals(object? other) + => this.Equals(other as ColumnSchema); + + public override int GetHashCode() + => this.Name.GetHashCode(); } diff --git a/src/TypeCache/Data/DataSource.cs b/src/TypeCache/Data/DataSource.cs index 0f1ae4c1..a79e9140 100644 --- a/src/TypeCache/Data/DataSource.cs +++ b/src/TypeCache/Data/DataSource.cs @@ -3,6 +3,7 @@ using System.Collections.Frozen; using System.Data; using System.Data.Common; +using System.Security.AccessControl; using TypeCache.Collections; using TypeCache.Data.Extensions; using TypeCache.Extensions; @@ -26,28 +27,28 @@ public DataSource(string name, DbProviderFactory dbProviderFactory, string conne this.Server = connection.DataSource; this.Version = connection.ServerVersion; - var @namespace = dbProviderFactory.GetType().Namespace ?? string.Empty; - this.Type = @namespace switch + var factoryName = dbProviderFactory.GetType().FullName!; + this.Type = factoryName switch { - _ when @namespace.Has("SqlClient") => DataSourceType.SqlServer, - _ when @namespace.Has("Oracle") => DataSourceType.Oracle, - _ when @namespace.Is("Npgsql") || @namespace.Has("Postgre") => DataSourceType.PostgreSql, - _ when @namespace.Has("MySql") => DataSourceType.MySql, - _ => DataSourceType.Unknown + _ when factoryName.Has("Oracle") => Oracle, + _ when factoryName.Has("Npgsql") || factoryName.Has("Postgre") => PostgreSql, + _ when factoryName.Has("MySql") => MySql, + _ when factoryName.Has("SqlClient") => SqlServer, + _ => Unknown }; - if (this.Type == DataSourceType.PostgreSql) + if (this.Type is PostgreSql) this.DefaultSchema = "public"; - else if (this.Type == DataSourceType.MySql) + else if (this.Type is MySql) this.DefaultSchema = this.DefaultDatabase; - else if (this.Type == DataSourceType.Unknown) + else if (this.Type is Unknown) this.DefaultSchema = string.Empty; else { var sql = this.Type switch { - DataSourceType.SqlServer => "SELECT SCHEMA_NAME();", - DataSourceType.Oracle => "SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM dual", + SqlServer => "SELECT SCHEMA_NAME();", + Oracle => "SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM dual", _ => string.Empty }; @@ -76,7 +77,7 @@ _ when @namespace.Has("MySql") => DataSourceType.MySql, } public ObjectSchema? this[string objectName] - => this.ObjectSchemas.TryGetValue(this.CreateName(objectName), out var objectSchema) ? objectSchema : null; + => this.ObjectSchemas.TryGetValue(this.Escape(objectName), out var objectSchema) ? objectSchema : null; public string ConnectionString { get; } @@ -90,7 +91,7 @@ public ObjectSchema? this[string objectName] public string Name { get; } - public IReadOnlyDictionary ObjectSchemas { get; } + public IReadOnlyDictionary ObjectSchemas { get; } public string Server { get; } @@ -104,42 +105,24 @@ public ObjectSchema? this[string objectName] public DbConnection CreateDbConnection() => this.Factory.CreateConnection(this.ConnectionString); - public DatabaseObject CreateName(string databaseObject) + public string Escape(string databaseObject) { - databaseObject.AssertNotNull(); + databaseObject.AssertNotBlank(); var items = databaseObject.Split('.', RemoveEmptyEntries | TrimEntries); if (items.Length > 3 || (this.Type == MySql && items.Length > 2)) - throw new ArgumentOutOfRangeException(Invariant($"{nameof(DataSource)}.{nameof(CreateName)}: Invalid name: {databaseObject}"), nameof(databaseObject)); + throw new ArgumentOutOfRangeException(Invariant($"{nameof(DataSource)}.{nameof(Escape)}: Invalid name: {databaseObject}"), nameof(databaseObject)); - items = items.Select(item => this.Type switch + items = (items.Length, this.Type) switch { - SqlServer when item[0] == '[' => item.TrimStart('[').TrimEnd(']'), - MySql => item.Trim('`').Replace("``", "`"), - _ => item.Trim('"').Replace("\"\"", "\"") - }).ToArray(); - - return items.Length switch - { - 1 => this.CreateName(this.DefaultSchema, items[0]), - 2 when databaseObject.Contains("..") => this.CreateName(items[0], this.DefaultSchema, items[1]), - 2 => this.CreateName(items[0], items[1]), - _ => this.CreateName(items[0], items[1], items[2]) - }; - } - - public DatabaseObject CreateName(string schema, string objectName) - => this.Type switch - { - MySql => new(Invariant($"{schema.EscapeIdentifier(this.Type)}.{objectName.EscapeIdentifier(this.Type)}")), - _ => new(Invariant($"{this.DefaultDatabase.EscapeIdentifier(this.Type)}.{schema.EscapeIdentifier(this.Type)}.{objectName.EscapeIdentifier(this.Type)}")) + (1, MySql) => [this.DefaultSchema, items[0]], + (1, _) => [this.DefaultDatabase, this.DefaultSchema, items[0]], + (2, _) when databaseObject.Contains("..") => [items[0], this.DefaultSchema, items[1]], + (2, _) => [this.DefaultDatabase, ..items], + _ => items }; - public DatabaseObject CreateName(string database, string schema, string objectName) - { - this.Databases.Contains(database).AssertTrue(); - - return new(Invariant($"{database.EscapeIdentifier(this.Type)}.{schema.EscapeIdentifier(this.Type)}.{objectName.EscapeIdentifier(this.Type)}")); + return '.'.Join(items.Select(item => item.UnEscapeIdentifier(this.Type).EscapeIdentifier(this.Type))); } [MethodImpl(AggressiveInlining), DebuggerHidden] @@ -223,9 +206,9 @@ private string[] GetDatabases() .Select()? .Select(row => row[SchemaColumn.database_name].ToString()!).ToArray() ?? Array.Empty; - private IReadOnlyDictionary GetObjectSchemas() + private IReadOnlyDictionary GetObjectSchemas() { - var objectSchemas = new Dictionary(); + var objectSchemas = new Dictionary(); using var connection = this.CreateDbConnection(); connection.Open(); @@ -250,9 +233,8 @@ private IReadOnlyDictionary GetObjectSchemas() { var tableName = tablesRow[SchemaColumn.table_name].ToString()!; var tableSchema = tablesRow[SchemaColumn.table_schema].ToString()!; - var name = this.CreateName(databaseName, tableSchema, tableName); - command.CommandText = Invariant($"SELECT * FROM {name} WHERE 0 = 1;"); + command.CommandText = Invariant($"SELECT * FROM {tableSchema.EscapeIdentifier(this.Type)}.{tableName.EscapeIdentifier(this.Type)} WHERE 0 = 1;"); var table = new DataTable(); try @@ -264,8 +246,8 @@ private IReadOnlyDictionary GetObjectSchemas() .Select(column => new ColumnSchema( column.ColumnName, column.AllowDBNull, table.PrimaryKey.Contains(column), column.ReadOnly, column.Unique, column.DataType.TypeHandle)); - var objectSchema = new ObjectSchema(this, DatabaseObjectType.Table, name, databaseName, tableSchema, tableName, columns); - objectSchemas.Add(name, objectSchema); + var objectSchema = new ObjectSchema(this, DatabaseObjectType.Table, databaseName, tableSchema, tableName, columns, null); + objectSchemas.Add(objectSchema.Name, objectSchema); } catch (Exception) { } }); @@ -279,9 +261,8 @@ private IReadOnlyDictionary GetObjectSchemas() { var tableName = viewsRow[SchemaColumn.table_name].ToString()!; var tableSchema = viewsRow[SchemaColumn.table_schema].ToString()!; - var name = this.CreateName(databaseName, tableSchema, tableName); - command.CommandText = Invariant($"SELECT * FROM {name} WHERE 0 = 1;"); + command.CommandText = Invariant($"SELECT * FROM {tableSchema.EscapeIdentifier(this.Type)}.{tableName.EscapeIdentifier(this.Type)} WHERE 0 = 1;"); var table = new DataTable(); try @@ -293,8 +274,8 @@ private IReadOnlyDictionary GetObjectSchemas() .Select(column => new ColumnSchema( column.ColumnName, column.AllowDBNull, table.PrimaryKey.Contains(column), column.ReadOnly, column.Unique, column.DataType.TypeHandle)); - var objectSchema = new ObjectSchema(this, DatabaseObjectType.View, name, databaseName, tableSchema, tableName, columns); - objectSchemas.Add(name, objectSchema); + var objectSchema = new ObjectSchema(this, DatabaseObjectType.View, databaseName, tableSchema, tableName, columns, null); + objectSchemas.Add(objectSchema.Name, objectSchema); } catch (Exception) { } }); @@ -321,14 +302,13 @@ string value when value.Is("INOUT") => ParameterDirection.InputOutput, _ => ParameterDirection.Input })); - var name = this.CreateName(databaseName, routineSchema, routineName); var objectType = routineType switch { _ when routineType.Is("FUNCTION") => DatabaseObjectType.Function, _ => DatabaseObjectType.StoredProcedure }; - var objectSchema = new ObjectSchema(this, objectType, name, databaseName, routineSchema, routineName, parameters ?? Array.Empty); - objectSchemas.Add(name, objectSchema); + var objectSchema = new ObjectSchema(this, objectType, databaseName, routineSchema, routineName, null, parameters); + objectSchemas.Add(objectSchema.Name, objectSchema); }); } }); diff --git a/src/TypeCache/Data/DatabaseObject.cs b/src/TypeCache/Data/DatabaseObject.cs deleted file mode 100644 index 242c946e..00000000 --- a/src/TypeCache/Data/DatabaseObject.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2021 Samuel Abraham - -using TypeCache.Extensions; - -namespace TypeCache.Data; - -public readonly struct DatabaseObject(string name) : IEquatable -{ - public string Name { get; } = name; - - [MethodImpl(AggressiveInlining), DebuggerHidden] - public bool Equals(DatabaseObject other) - => this.Name.Is(other.Name); - - [MethodImpl(AggressiveInlining), DebuggerHidden] - public override int GetHashCode() - => this.Name.GetHashCode(); - - [MethodImpl(AggressiveInlining), DebuggerHidden] - public override string ToString() - => this.Name; - - [MethodImpl(AggressiveInlining), DebuggerHidden] - public static implicit operator string(DatabaseObject _) - => _.Name; - - [MethodImpl(AggressiveInlining), DebuggerHidden] - public static implicit operator ReadOnlySpan(DatabaseObject _) - => _.Name; -} diff --git a/src/TypeCache/Data/Extensions/SqlExtensions.cs b/src/TypeCache/Data/Extensions/SqlExtensions.cs index 5276eaa4..7785540b 100644 --- a/src/TypeCache/Data/Extensions/SqlExtensions.cs +++ b/src/TypeCache/Data/Extensions/SqlExtensions.cs @@ -16,9 +16,8 @@ public static string EscapeIdentifier([NotNull] this string @this, DataSourceTyp => type switch { SqlServer => Invariant($"[{@this.Replace("]", "]]")}]"), - Oracle or PostgreSql => Invariant($"\"{@this.Replace("\"", "\"\"")}\""), MySql => Invariant($"`{@this.Replace("`", "``")}`"), - _ => @this + _ => Invariant($"\"{@this.Replace("\"", "\"\"")}\""), }; /// @@ -53,11 +52,11 @@ public static string EscapeValue([NotNull] this string @this) Guid guid => Invariant($"'{guid:D}'"), LogicalOperator.And => "AND", LogicalOperator.Or => "OR", - LogicalOperator value => throw new UnreachableException(Invariant($"{nameof(LogicalOperator)}.{value:F} is not implemented for SQL.")), + LogicalOperator value => throw new UnreachableException(Invariant($"{nameof(LogicalOperator)}.{value.Name()} is not implemented for SQL.")), Sort.Ascending => "ASC", Sort.Descending => "DESC", - Sort value => throw new UnreachableException(Invariant($"{nameof(Sort)}.{value:F} is not implemented for SQL.")), - Enum token => token.ToString("D"), + Sort value => throw new UnreachableException(Invariant($"{nameof(Sort)}.{value.Name()} is not implemented for SQL.")), + Enum token => token.Name(), Range range => Invariant($"'{range}'"), Uri uri => Invariant($"'{uri.ToString().EscapeValue()}'"), byte[] binary => Invariant($"0x{binary.ToHexString()}"), @@ -79,4 +78,12 @@ public static string EscapeValue([NotNull] this string @this) IEnumerable enumerable => Invariant($"({enumerable.Cast().Select(_ => _.ToSQL()).ToCSV()})"), _ => @this.ToString() ?? "NULL" }; + + public static string UnEscapeIdentifier([NotNull] this string @this, DataSourceType type) + => type switch + { + SqlServer => @this.TrimStart('[').TrimEnd(']').Replace("]]", "]"), + MySql => @this.Trim('`').Replace("``", "`"), + _ => @this.Trim('"').Replace("\"\"", "\"") + }; } diff --git a/src/TypeCache/Data/IDataSource.cs b/src/TypeCache/Data/IDataSource.cs index c2274ca7..bf3655cd 100644 --- a/src/TypeCache/Data/IDataSource.cs +++ b/src/TypeCache/Data/IDataSource.cs @@ -19,7 +19,7 @@ public interface IDataSource : IEquatable string Name { get; } - IReadOnlyDictionary ObjectSchemas { get; } + IReadOnlyDictionary ObjectSchemas { get; } public string Server { get; } @@ -34,18 +34,7 @@ public interface IDataSource : IEquatable DbConnection CreateDbConnection(); /// - DatabaseObject CreateName(string databaseObject); - - /// - /// => (($"{.EscapeIdentifier(.DefaultDatabase)}.{.EscapeIdentifier()}.{.EscapeIdentifier()}")); - /// - DatabaseObject CreateName(string schema, string objectName); - - /// - /// => (($"{.EscapeIdentifier()}.{.EscapeIdentifier()}.{.EscapeIdentifier()}")); - /// - /// - DatabaseObject CreateName(string database, string schema, string objectName); + string Escape(string databaseObject); SqlCommand CreateSqlCommand(string sql); diff --git a/src/TypeCache/Data/ObjectSchema.cs b/src/TypeCache/Data/ObjectSchema.cs index 20c53e71..33d8a40f 100644 --- a/src/TypeCache/Data/ObjectSchema.cs +++ b/src/TypeCache/Data/ObjectSchema.cs @@ -1,5 +1,6 @@ // Copyright (c) 2021 Samuel Abraham +using System.Collections.Frozen; using System.Collections.Immutable; using System.Data; using System.Text; @@ -11,39 +12,35 @@ namespace TypeCache.Data; -public sealed record ObjectSchema(IDataSource DataSource, DatabaseObjectType Type, DatabaseObject Name, string DatabaseName, string SchemaName, string ObjectName) -{ - public ObjectSchema( - IDataSource dataSource +public sealed class ObjectSchema( + IDataSource dataSource , DatabaseObjectType type - , DatabaseObject name , string databaseName , string schemaName , string objectName - , IEnumerable columns - ) : this(dataSource, type, name, databaseName, schemaName, objectName) - { - this.Columns = columns.ToImmutableArray(); - } + , IEnumerable? columns + , IEnumerable? parameters + ) : IEquatable +{ + public IDataSource DataSource { get; } = dataSource; - public ObjectSchema( - IDataSource dataSource - , DatabaseObjectType type - , DatabaseObject name - , string databaseName - , string schemaName - , string objectName - , IEnumerable parameters - ) : this(dataSource, type, name, databaseName, schemaName, objectName) + public DatabaseObjectType Type { get; } = type; + + public string Name { get; } = dataSource.Type switch { - this.Parameters = parameters.ToImmutableArray(); - } + MySql => Invariant($"{schemaName.EscapeIdentifier(dataSource.Type)}.{objectName.EscapeIdentifier(dataSource.Type)}"), + _ => Invariant($"{databaseName.EscapeIdentifier(dataSource.Type)}.{schemaName.EscapeIdentifier(dataSource.Type)}.{objectName.EscapeIdentifier(dataSource.Type)}") + }; - [DebuggerHidden] - public IReadOnlyList Columns { get; } = ImmutableArray.Empty; + public string DatabaseName { get; } = databaseName; - [DebuggerHidden] - public IReadOnlyList Parameters { get; } = ImmutableArray.Empty; + public string SchemaName { get; } = schemaName; + + public string ObjectName { get; } = objectName; + + public IReadOnlySet Columns { get; } = columns?.ToFrozenSet() ?? FrozenSet.Empty; + + public IReadOnlySet Parameters { get; } = parameters?.ToFrozenSet() ?? FrozenSet.Empty; public DataTable CreateDataTable() { @@ -196,7 +193,8 @@ public string CreateSelectSQL(SelectQuery select) case SqlServer: sqlBuilder .AppendIf(select.Distinct, "DISTINCT ") - .AppendIf(select.Top.IsNotBlank(), Invariant($"TOP {select.Top} ")); + .AppendIf(select.Top.HasValue, Invariant($"TOP ({select.Top}) ")) + .AppendIf(select.Top.HasValue && select.TopPercent, "PERCENT "); break; case Oracle: case MySql: diff --git a/src/TypeCache/Data/ParameterSchema.cs b/src/TypeCache/Data/ParameterSchema.cs index db201de1..3501fa27 100644 --- a/src/TypeCache/Data/ParameterSchema.cs +++ b/src/TypeCache/Data/ParameterSchema.cs @@ -1,9 +1,22 @@ // Copyright (c) 2021 Samuel Abraham using System.Data; +using TypeCache.Extensions; namespace TypeCache.Data; -public sealed record ParameterSchema(string Name, ParameterDirection Direction) +public sealed class ParameterSchema(string name, ParameterDirection direction) : IEquatable { + public string Name { get; } = name; + + public ParameterDirection Direction { get; } = direction; + + public bool Equals(ParameterSchema? other) + => this.Name.Is(other?.Name); + + public override bool Equals(object? other) + => this.Equals(other as ParameterSchema); + + public override int GetHashCode() + => this.Name.GetHashCode(); } diff --git a/src/TypeCache/Data/SelectQuery.cs b/src/TypeCache/Data/SelectQuery.cs index 777c0027..ed84168a 100644 --- a/src/TypeCache/Data/SelectQuery.cs +++ b/src/TypeCache/Data/SelectQuery.cs @@ -19,14 +19,14 @@ public class SelectQuery public string? DistinctOn { get; set; } /// - /// FETCH NEXT 100 ROWS ONLY + /// FETCH NEXT 100 ROWS ONLY /// public uint Fetch { get; set; } /// - /// FROM [Table] + /// FROM [Table]{ AS Alias} /// - public DatabaseObject From { get; set; } + public string? From { get; set; } /// /// GROUP BY [Column1], TRIM([Column 2]), Column3 @@ -70,10 +70,16 @@ public class SelectQuery public string? TableHints { get; set; } /// - /// SELECT TOP (100) + /// SELECT TOP 100
///
/// * * * SQL Server only * * * - public string? Top { get; set; } + public uint? Top { get; set; } + + /// + /// SELECT TOP 10 PERCENT + /// + /// * * * SQL Server only * * * + public bool TopPercent { get; set; } /// /// diff --git a/src/TypeCache/Extensions/DateTimeExtensions.cs b/src/TypeCache/Extensions/DateTimeExtensions.cs index 928e2dee..4f85e0f8 100644 --- a/src/TypeCache/Extensions/DateTimeExtensions.cs +++ b/src/TypeCache/Extensions/DateTimeExtensions.cs @@ -52,7 +52,7 @@ public static DateOnly ToDateOnly(this DateTimeOffset @this) /// [MethodImpl(AggressiveInlining), DebuggerHidden] public static DateTimeOffset ToDateTimeOffset(this DateTime @this) - => new DateTimeOffset(@this); + => new(@this); /// /// @@ -60,7 +60,7 @@ public static DateTimeOffset ToDateTimeOffset(this DateTime @this) /// [MethodImpl(AggressiveInlining), DebuggerHidden] public static DateTimeOffset ToDateTimeOffset(this DateTime @this, TimeSpan offset) - => new DateTimeOffset(@this, offset); + => new(@this, offset); /// /// @@ -78,8 +78,6 @@ public static TimeOnly ToTimeOnly(this DateTime @this) public static TimeOnly ToTimeOnly(this DateTimeOffset @this) => TimeOnly.FromDateTime(@this.DateTime); - /// - /// /// public static DateTime ToTimeZone(this DateTime @this, DateTimeKind kind) => kind switch @@ -91,7 +89,7 @@ public static DateTime ToTimeZone(this DateTime @this, DateTimeKind kind) _ => throw new UnreachableException($"{nameof(ToTimeZone)}: {nameof(DateTimeKind)} value of {kind:D} is not supported.") }; - /// if: @.Kind = + /// public static DateTime ToTimeZone(this DateTime @this, TimeZoneInfo targetTimeZone) => @this.Kind switch { diff --git a/src/TypeCache/Extensions/DictionaryExtensions.cs b/src/TypeCache/Extensions/DictionaryExtensions.cs index cf27c6ca..1928556b 100644 --- a/src/TypeCache/Extensions/DictionaryExtensions.cs +++ b/src/TypeCache/Extensions/DictionaryExtensions.cs @@ -45,9 +45,10 @@ public static bool If(this IDictionary @this, K key, Action ac return success; } - /// + /// + /// /// => ReadOnlyDictionary<, >(@); - /// + /// [MethodImpl(AggressiveInlining), DebuggerHidden] public static IReadOnlyDictionary ToReadOnly(this IDictionary @this) where K : notnull diff --git a/src/TypeCache/Extensions/EnumExtensions.cs b/src/TypeCache/Extensions/EnumExtensions.cs index ea439398..e6f7f8eb 100644 --- a/src/TypeCache/Extensions/EnumExtensions.cs +++ b/src/TypeCache/Extensions/EnumExtensions.cs @@ -21,6 +21,13 @@ public static bool HasAnyFlag(this T @this, T[] flags) where T : struct, Enum => flags?.Any(flag => @this.HasFlag(flag)) ?? false; + /// + /// => @.ToString("X"); + /// + [MethodImpl(AggressiveInlining), DebuggerHidden] + public static string Hex(this Enum @this) + => @this.ToString("X"); + /// /// => @.ToString("X"); /// @@ -48,11 +55,25 @@ public static bool IsValid(this T @this) /// /// => @.ToString("F"); /// - [DebuggerHidden] + [MethodImpl(AggressiveInlining), DebuggerHidden] + public static string Name(this Enum @this) + => @this.ToString("F"); + + /// + /// => @.ToString("F"); + /// + [MethodImpl(AggressiveInlining), DebuggerHidden] public static string Name(this T @this) where T : struct, Enum => @this.ToString("F"); + /// + /// => @.ToString("D"); + /// + [MethodImpl(AggressiveInlining), DebuggerHidden] + public static string Number(this Enum @this) + => @this.ToString("D"); + /// /// => @.ToString("D"); /// diff --git a/src/TypeCache/Extensions/EnumerableExtensions.cs b/src/TypeCache/Extensions/EnumerableExtensions.cs index 7f14b556..04614cac 100644 --- a/src/TypeCache/Extensions/EnumerableExtensions.cs +++ b/src/TypeCache/Extensions/EnumerableExtensions.cs @@ -23,18 +23,18 @@ public static T[] AsArray(this IEnumerable? @this) => @this as T[] ?? @this?.ToArray() ?? Array.Empty; /// - /// => @ ?? @?.ToHashSet() ?? (0); + /// => @ ?? @?.ToHashSet() ?? (0, ); /// [MethodImpl(AggressiveInlining), DebuggerHidden] - public static HashSet AsHashSet(this IEnumerable? @this) - => @this as HashSet ?? @this?.ToHashSet() ?? new HashSet(0); + public static ISet AsHashSet(this IEnumerable? @this, IEqualityComparer? comparer = null) + => @this as ISet ?? @this?.ToHashSet(comparer) ?? new HashSet(0, comparer); /// - /// => @ ?? @?.ToList() ?? (0); + /// => @ ?? @?.ToList() ?? (0); /// [MethodImpl(AggressiveInlining), DebuggerHidden] - public static List AsList(this IEnumerable? @this) - => @this as List ?? @this?.ToList() ?? new List(0); + public static IList AsList(this IEnumerable? @this) + => @this as IList ?? @this?.ToList() ?? new List(0); /// /// => @.Contains(, ()); @@ -123,7 +123,7 @@ public static void Deconstruct(this IEnumerable @this, out T? first, out T [MethodImpl(AggressiveInlining), DebuggerHidden] public static Dictionary ToDictionary(this IEnumerable> @this, IEqualityComparer? comparer = null) where TKey : notnull - => new Dictionary(@this, comparer); + => new(@this, comparer); /// /// => @.Select(_ => _.AsTask()); diff --git a/src/TypeCache/Extensions/ReflectionExtensions.Handles.cs b/src/TypeCache/Extensions/ReflectionExtensions.Handles.cs index 12dd6961..b33f5a73 100644 --- a/src/TypeCache/Extensions/ReflectionExtensions.Handles.cs +++ b/src/TypeCache/Extensions/ReflectionExtensions.Handles.cs @@ -4,7 +4,7 @@ namespace TypeCache.Extensions; -partial class ReflectionExtensions +public partial class ReflectionExtensions { /// /// diff --git a/src/TypeCache/Extensions/ReflectionExtensions.MethodBase.cs b/src/TypeCache/Extensions/ReflectionExtensions.MethodBase.cs index f29bbe68..e7274a14 100644 --- a/src/TypeCache/Extensions/ReflectionExtensions.MethodBase.cs +++ b/src/TypeCache/Extensions/ReflectionExtensions.MethodBase.cs @@ -5,7 +5,7 @@ namespace TypeCache.Extensions; -partial class ReflectionExtensions +public partial class ReflectionExtensions { public static bool IsCallableWith(this MethodBase @this, object?[]? arguments) { diff --git a/src/TypeCache/Extensions/ReflectionExtensions.ObjectType.cs b/src/TypeCache/Extensions/ReflectionExtensions.ObjectType.cs index 1c8dc314..429da0ee 100644 --- a/src/TypeCache/Extensions/ReflectionExtensions.ObjectType.cs +++ b/src/TypeCache/Extensions/ReflectionExtensions.ObjectType.cs @@ -140,7 +140,7 @@ public enum ObjectType /// ValueTuple, /// - /// Is: + /// Is: /// Void, /// diff --git a/src/TypeCache/Extensions/ReflectionExtensions.ParameterInfo.cs b/src/TypeCache/Extensions/ReflectionExtensions.ParameterInfo.cs index 9d51f7af..ff09546c 100644 --- a/src/TypeCache/Extensions/ReflectionExtensions.ParameterInfo.cs +++ b/src/TypeCache/Extensions/ReflectionExtensions.ParameterInfo.cs @@ -2,11 +2,10 @@ using System.Linq.Expressions; using System.Reflection; -using TypeCache.Attributes; namespace TypeCache.Extensions; -partial class ReflectionExtensions +public partial class ReflectionExtensions { /// /// @.GetCustomAttribute<>() ; diff --git a/src/TypeCache/Extensions/ReflectionExtensions.ScalarType.cs b/src/TypeCache/Extensions/ReflectionExtensions.ScalarType.cs index 52444720..734ca1ee 100644 --- a/src/TypeCache/Extensions/ReflectionExtensions.ScalarType.cs +++ b/src/TypeCache/Extensions/ReflectionExtensions.ScalarType.cs @@ -10,15 +10,15 @@ public enum ScalarType /// BigInteger, /// - /// + /// /// Boolean, /// - /// + /// /// Byte, /// - /// + /// /// Char, /// @@ -34,11 +34,11 @@ public enum ScalarType /// DateTimeOffset, /// - /// + /// /// Decimal, /// - /// + /// /// Double, /// @@ -62,31 +62,31 @@ public enum ScalarType /// Int128, /// - /// + /// /// Int16, /// - /// + /// /// Int32, /// - /// + /// /// Int64, /// - /// + /// /// IntPtr, /// - /// + /// /// SByte, /// - /// + /// /// Single, /// - /// + /// /// String, /// @@ -102,19 +102,19 @@ public enum ScalarType /// UInt128, /// - /// + /// /// UInt16, /// - /// + /// /// UInt32, /// - /// + /// /// UInt64, /// - /// + /// /// UIntPtr, /// diff --git a/src/TypeCache/Extensions/ReflectionExtensions.Type.cs b/src/TypeCache/Extensions/ReflectionExtensions.Type.cs index cdec1b9a..ab7b2070 100644 --- a/src/TypeCache/Extensions/ReflectionExtensions.Type.cs +++ b/src/TypeCache/Extensions/ReflectionExtensions.Type.cs @@ -41,7 +41,7 @@ public partial class ReflectionExtensions [DebuggerHidden] public static MethodInfo? FindMethod(this Type @this, string name, Type[] genericTypes, object?[]? arguments = null) => @this.GetMethods(INSTANCE_BINDING_FLAGS) - .Where(method => method.Name.Is(name) && method.IsGenericMethod) + .Where(method => method.Name.Is(name) && method.IsGenericMethod && method.GetGenericArguments().Length == genericTypes.Length) .FirstOrDefault(method => method.MakeGenericMethod(genericTypes)?.IsCallableWith(arguments) is true); /// @@ -77,8 +77,12 @@ public static ScalarType GetScalarType(this Type @this) { { IsGenericTypeDefinition: true } => ScalarType.None, { IsEnum: true } => ScalarType.Enum, - { IsGenericType: true } when @this.IsNullable() && TypeStore.DataTypes.TryGetValue(@this.GenericTypeArguments[0].TypeHandle, out var scalarType) - => scalarType, + { IsGenericType: true } when @this.IsNullable() => @this.GenericTypeArguments[0] switch + { + { IsEnum: true } => ScalarType.Enum, + Type type when TypeStore.DataTypes.TryGetValue(type.TypeHandle, out var scalarType) => scalarType, + _ => ScalarType.None + }, _ when TypeStore.DataTypes.TryGetValue(@this.TypeHandle, out var scalarType) => scalarType, _ => ScalarType.None }; diff --git a/src/TypeCache/Extensions/RulesBuilderExtensions.cs b/src/TypeCache/Extensions/RulesBuilderExtensions.cs deleted file mode 100644 index 89e1f038..00000000 --- a/src/TypeCache/Extensions/RulesBuilderExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2021 Samuel Abraham - -using System.Data; -using System.Text.Json.Nodes; -using Microsoft.Extensions.DependencyInjection; -using TypeCache.Data.Mediation; -using TypeCache.Mediation; - -namespace TypeCache.Extensions; - -public static class RulesBuilderExtensions -{ - /// - /// => @.AddSingleton<IRule<, >, >()
- /// .AddSingleton<IRule<, >, >()
- /// .AddSingleton<IRule<, >()
- /// .AddSingleton<IRule<, >, >()
- /// .AddSingleton<IRule<, IList<>>, >()
- /// .AddSingleton<IRule<, >, >()
- ///
- /// Requires calls to: - /// - ///
- ///
- ///
- public static RulesBuilder AddSqlCommandRules(this RulesBuilder @this) - => @this.AddRule(new SqlDataSetRule()) - .AddRule(new SqlDataSetRule()) - .AddRule(new SqlDataTableRule()) - .AddRule(new SqlExecuteRule()) - .AddRule(new SqlJsonArrayRule()) - .AddRule>(new SqlModelsRule()) - .AddRule(new SqlScalarRule()); -} diff --git a/src/TypeCache/Extensions/ServiceCollectionExtensions.cs b/src/TypeCache/Extensions/ServiceCollectionExtensions.cs index 972344e8..607f9f1c 100644 --- a/src/TypeCache/Extensions/ServiceCollectionExtensions.cs +++ b/src/TypeCache/Extensions/ServiceCollectionExtensions.cs @@ -3,10 +3,12 @@ using System.Data; using System.Data.Common; using System.Reflection; +using System.Text.Json.Nodes; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using TypeCache.Attributes; using TypeCache.Data; +using TypeCache.Data.Mediation; using TypeCache.Mediation; using TypeCache.Net.Mediation; using TypeCache.Utilities; @@ -15,6 +17,156 @@ namespace TypeCache.Extensions; public static class ServiceCollectionExtensions { + /// + /// Registers singleton: .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, IAfterRule afterRule) + where REQUEST : IRequest + => @this.AddSingleton>(afterRule); + + /// + /// Registers named singleton: .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, string name, IAfterRule afterRule) + where REQUEST : IRequest + => @this.AddKeyedSingleton>(name, afterRule); + + /// + /// Registers singleton: .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, IAfterRule afterRule) + where REQUEST : IRequest + => @this.AddSingleton>(afterRule); + + /// + /// Registers named singleton: .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, string name, IAfterRule afterRule) + where REQUEST : IRequest + => @this.AddKeyedSingleton>(name, afterRule); + + /// + /// Registers an implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func> createAfterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, createAfterRule); + + /// + /// Registers a named implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Func> createAfterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => createAfterRule(provider)); + + /// + /// Registers an implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func afterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, provider => RuleFactory.CreateAfterRule(afterRule)); + + /// + /// Registers a named implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Func afterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => RuleFactory.CreateAfterRule(afterRule)); + + /// + /// Registers an implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Action afterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, provider => RuleFactory.CreateAfterRule(afterRule)); + + /// + /// Registers a named implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Action afterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => RuleFactory.CreateAfterRule(afterRule)); + + /// + /// Registers an implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func> createAfterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, createAfterRule); + + /// + /// Registers an implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func afterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, provider => RuleFactory.CreateAfterRule(afterRule)); + + /// + /// Registers a named implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Func afterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => RuleFactory.CreateAfterRule(afterRule)); + + /// + /// Registers an implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Action afterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, provider => RuleFactory.CreateAfterRule(afterRule)); + + /// + /// Registers a named implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddAfterRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Action afterRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => RuleFactory.CreateAfterRule(afterRule)); + /// /// Registers keyed singleton .
///
@@ -63,17 +215,196 @@ public static IServiceCollection AddHttpClientRule(this IServiceCollection @this /// /// Registers Singleton:
- /// Optionally can register Rules and After Rules. + /// - manually register Rules, Validation Rules and After Rules. + ///
+ public static IServiceCollection AddMediation(this IServiceCollection @this) + => @this.AddSingleton(); + + /// + /// Registers singleton: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, IRule rule) + where REQUEST : IRequest + => @this.AddSingleton>(rule); + + /// + /// Registers named singleton: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, string name, IRule rule) + where REQUEST : IRequest + => @this.AddKeyedSingleton>(name, rule); + + /// + /// Registers singleton: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, IRule rule) + where REQUEST : IRequest + => @this.AddSingleton>(rule); + + /// + /// Registers named singleton: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, string name, IRule rule) + where REQUEST : IRequest + => @this.AddKeyedSingleton>(name, rule); + + /// + /// Registers an implementation of .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func> createRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, createRule); + + /// + /// Registers an implementation of .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func rule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, provider => RuleFactory.CreateRule(rule)); + + /// + /// Registers a named implementation of .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Func rule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => RuleFactory.CreateRule(rule)); + + /// + /// Registers an implementation of .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Action rule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, provider => RuleFactory.CreateRule(rule)); + + /// + /// Registers a named implementation of .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Action rule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => RuleFactory.CreateRule(rule)); + + /// + /// Registers an implementation of .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func> createRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, createRule); + + /// + /// Registers a named implementation of .
+ /// Requires call to: ///
- public static IServiceCollection AddMediation(this IServiceCollection @this, Action? rulesBuilder = null) + /// + public static IServiceCollection AddRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Func> createRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => createRule(provider)); + + /// + /// Registers an implementation of .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func> rule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, provider => RuleFactory.CreateRule(rule)); + + /// + /// Registers a named implementation of .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Func> rule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => RuleFactory.CreateRule(rule)); + + /// + /// Registers an implementation of .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func rule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, provider => RuleFactory.CreateRule(rule)); + + /// + /// Registers a named implementation of .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Func rule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => RuleFactory.CreateRule(rule)); + + public static IServiceCollection AddServiceDescriptor(this IServiceCollection @this, ServiceLifetime serviceLifetime) + { + @this.Add(ServiceDescriptor.Describe(typeof(SERVICE), typeof(IMPLEMENTATION), serviceLifetime)); + return @this; + } + + public static IServiceCollection AddServiceDescriptor(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func factory) + where T : class + { + @this.Add(ServiceDescriptor.Describe(typeof(T), factory, serviceLifetime)); + return @this; + } + + public static IServiceCollection AddServiceDescriptor(this IServiceCollection @this, object? key, ServiceLifetime serviceLifetime) + { + @this.Add(ServiceDescriptor.DescribeKeyed(typeof(SERVICE), key, typeof(IMPLEMENTATION), serviceLifetime)); + return @this; + } + + public static IServiceCollection AddServiceDescriptor(this IServiceCollection @this, object? key, ServiceLifetime serviceLifetime, Func factory) + where T : class { - @this.AddSingleton(); - rulesBuilder?.Invoke(new RulesBuilder(@this)); + @this.Add(ServiceDescriptor.DescribeKeyed(typeof(T), key, factory, serviceLifetime)); return @this; } /// - /// Registers all types in the specified assembly that have or . + /// => @.AddSingleton<IRule<, >, >()
+ /// .AddSingleton<IRule<, >, >()
+ /// .AddSingleton<IRule<, >()
+ /// .AddSingleton<IRule<, >, >()
+ /// .AddSingleton<IRule<, IList<>>, >()
+ /// .AddSingleton<IRule<, >, >()
+ ///
+ /// Requires calls to: + /// + ///
+ ///
+ ///
+ public static IServiceCollection AddSqlCommandRules(this IServiceCollection @this) + => @this.AddRule(new SqlDataSetRule()) + .AddRule(new SqlDataSetRule()) + .AddRule(new SqlDataTableRule()) + .AddRule(new SqlExecuteRule()) + .AddRule(new SqlJsonArrayRule()) + .AddRule>(new SqlModelsRule()) + .AddRule(new SqlScalarRule()); + + /// + /// Registers all types in the specified assembly that have or . /// /// The assembly to register the types from. public static IServiceCollection AddTypes(this IServiceCollection @this, Assembly fromAssembly) @@ -84,11 +415,72 @@ public static IServiceCollection AddTypes(this IServiceCollection @this, Assembl .Select(implementationType => { var attribute = implementationType.GetCustomAttribute()!; - return new ServiceDescriptor(attribute.ServiceType ?? implementationType, implementationType, attribute.ServiceLifetime); + return attribute.Key is not null + ? ServiceDescriptor.DescribeKeyed(attribute.ServiceType ?? implementationType, attribute.Key, implementationType, attribute.ServiceLifetime) + : ServiceDescriptor.Describe(attribute.ServiceType ?? implementationType, implementationType, attribute.ServiceLifetime); }) .ToArray(); serviceDescriptors.ForEach(@this.Add); return @this; } + + /// + /// Registers singleton: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddValidationRule(this IServiceCollection @this, IValidationRule validationRule) + where REQUEST : IRequest + => @this.AddSingleton>(validationRule); + + /// + /// Registers named singleton: .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddValidationRule(this IServiceCollection @this, string name, IValidationRule validationRule) + where REQUEST : IRequest + => @this.AddKeyedSingleton>(name, validationRule); + + /// + /// Registers an implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddValidationRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Func validationRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, provider => RuleFactory.CreateValidationRule(validationRule)); + + /// + /// Registers a named implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddValidationRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Func validationRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => RuleFactory.CreateValidationRule(validationRule)); + + /// + /// Registers an implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddValidationRule(this IServiceCollection @this, ServiceLifetime serviceLifetime, Action validationRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(serviceLifetime, provider => RuleFactory.CreateValidationRule(validationRule)); + + /// + /// Registers a named implementation of .
+ /// Requires registering rule: .
+ /// Requires call to: + ///
+ /// + public static IServiceCollection AddValidationRule(this IServiceCollection @this, string name, ServiceLifetime serviceLifetime, Action validationRule) + where REQUEST : IRequest + => @this.AddServiceDescriptor>(name, serviceLifetime, (provider, name) => RuleFactory.CreateValidationRule(validationRule)); } diff --git a/src/TypeCache/Extensions/StringBuilderExtensions.cs b/src/TypeCache/Extensions/StringBuilderExtensions.cs index d553e3cf..b191ec18 100644 --- a/src/TypeCache/Extensions/StringBuilderExtensions.cs +++ b/src/TypeCache/Extensions/StringBuilderExtensions.cs @@ -61,7 +61,7 @@ public static StringBuilder AppendIf(this StringBuilder @this, bool condition float item => @this.Append(item), double item => @this.Append(item), decimal item => @this.Append(item), - Enum item => @this.Append(item.ToString("F")), + Enum item => @this.Append(item.Name()), char[] item => @this.Append(item), string item => @this.Append(item), StringBuilder item => @this.Append(item), diff --git a/src/TypeCache/Extensions/StringExtensions.cs b/src/TypeCache/Extensions/StringExtensions.cs index 9e28a5c6..cfe9c80f 100644 --- a/src/TypeCache/Extensions/StringExtensions.cs +++ b/src/TypeCache/Extensions/StringExtensions.cs @@ -107,18 +107,18 @@ public static string FromBase64(this string @this, Encoding encoding) } /// - /// => @.Contains(, ); + /// => @.Contains(, ); /// [MethodImpl(AggressiveInlining), DebuggerHidden] - public static bool Has(this string @this, string value, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - => @this.Contains(value, comparison); + public static bool Has(this string @this, string value) + => @this.Contains(value, StringComparison.OrdinalIgnoreCase); /// - /// => .Equals(@, , ); + /// => .Equals(@, , ); /// [MethodImpl(AggressiveInlining), DebuggerHidden] - public static bool Is(this string? @this, string? value, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - => string.Equals(@this, value, comparison); + public static bool Is([NotNullWhen(true)] this string? @this, [NotNullWhen(true)] string? value) + => string.Equals(@this, value, StringComparison.OrdinalIgnoreCase); /// /// @@ -184,11 +184,11 @@ public static string Left(this string @this, int length) /// /// - /// => @.StartsWith(, ); + /// => @.StartsWith(, ); /// [MethodImpl(AggressiveInlining), DebuggerHidden] - public static bool Left(this string @this, string text, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - => @this.StartsWith(text, comparison); + public static bool Left(this string @this, string text) + => @this.StartsWith(text, StringComparison.OrdinalIgnoreCase); public static string Mask(this string @this, char mask = '*') { @@ -270,12 +270,12 @@ public static string MaskShow(this string @this, char mask = '*', StringComparis => @this.IsNotBlank() ? @this : null; /// - /// => @.IsNotEmpty() ? @ : ; + /// => @ != ? @ : ; /// [MethodImpl(AggressiveInlining), DebuggerHidden] [return: NotNullIfNotNull("this")] public static string? NullIfEmpty(this string? @this) - => @this.IsNotNullOrEmpty() ? @this : null; + => @this != string.Empty ? @this : null; /// /// @@ -329,27 +329,11 @@ public static bool Right(this string @this, char text) /// /// - /// => @.EndsWith(, ); + /// => @.EndsWith(, ); /// [MethodImpl(AggressiveInlining), DebuggerHidden] - public static bool Right(this string @this, string text, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - => @this.EndsWith(text, comparison); - - /// - /// - /// => (@); - /// - [MethodImpl(AggressiveInlining), DebuggerHidden] - public static StringSegment Segment(this string? @this) - => new(@this); - - /// - /// - /// => (@, , ); - /// - [MethodImpl(AggressiveInlining), DebuggerHidden] - public static StringSegment Segment(this string @this, int offset, int count) - => new(@this, offset, count); + public static bool Right(this string @this, string text) + => @this.EndsWith(text, StringComparison.OrdinalIgnoreCase); public static string ToBase64(this string @this, Encoding encoding, bool stripPadding = false) { @@ -359,7 +343,7 @@ public static string ToBase64(this string @this, Encoding encoding, bool stripPa Span chars = stackalloc char[length * sizeof(char)]; return Convert.TryToBase64Chars(bytes, chars, out var count) - ? new string(chars.Slice(0, stripPadding ? count - 2 : count)) + ? new(chars.Slice(0, stripPadding ? count - 2 : count)) : string.Empty; } @@ -396,25 +380,51 @@ public static ParameterExpression ToParameterExpression(this string @this) public static ParameterExpression ToParameterExpression(this string @this, Type type) => Expression.Parameter(type, @this); + /// + /// + /// => RegexCache[(@, )]; + /// // _ => new Regex(@, , .FromMinutes(1)); + /// + /// + [MethodImpl(AggressiveInlining), DebuggerHidden] + public static Regex ToRegex([StringSyntax(StringSyntaxAttribute.Regex)] this string @this, RegexOptions options = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline) + => RegexCache[(@this, options)]; + + /// + /// + /// => (@); + /// + [MethodImpl(AggressiveInlining), DebuggerHidden] + public static StringSegment ToStringSegment(this string? @this) + => new(@this); + + /// + /// + /// => (@, , ); + /// + [MethodImpl(AggressiveInlining), DebuggerHidden] + public static StringSegment ToStringSegment(this string @this, int offset, int count) + => new(@this, offset, count); + /// /// => @ ? (@) : ; /// [MethodImpl(AggressiveInlining), DebuggerHidden] public static Uri? ToUri(this string? @this) - => @this is not null ? new Uri(@this) : null; + => @this is not null ? new(@this) : null; /// /// => @ ? (@, ) : ; /// [MethodImpl(AggressiveInlining), DebuggerHidden] public static Uri? ToUri(this string? @this, UriKind kind) - => @this is not null ? new Uri(@this, kind) : null; + => @this is not null ? new(@this, kind) : null; public static string TrimEnd(this string @this, string text, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - => text.IsNotBlank() && @this?.Right(text, comparison) is true ? @this.Substring(0, @this.Length - text.Length) : (@this ?? string.Empty); + => text.IsNotBlank() && @this?.EndsWith(text, comparison) is true ? @this.Substring(0, @this.Length - text.Length) : (@this ?? string.Empty); public static string TrimStart(this string @this, string text, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - => text.IsNotBlank() && @this?.Left(text, comparison) is true ? @this.Substring(text.Length) : (@this ?? string.Empty); + => text.IsNotBlank() && @this?.StartsWith(text, comparison) is true ? @this.Substring(text.Length) : (@this ?? string.Empty); /// /// @@ -442,4 +452,7 @@ public static bool TryParse([NotNullWhen(true)] this string? @this, IFormatPr public static bool TryParse([NotNullWhen(true)] this string? @this, NumberStyles style, IFormatProvider? formatProvider, [MaybeNullWhen(false)] out T value) where T : INumberBase => T.TryParse(@this, style, formatProvider ?? InvariantCulture, out value); + + private static readonly IReadOnlyDictionary<(string Pattern, RegexOptions Options), Regex> RegexCache = + new LazyDictionary<(string Pattern, RegexOptions Options), Regex>(_ => new(_.Pattern, _.Options, TimeSpan.FromMinutes(1))); } diff --git a/src/TypeCache/Mediation/IMediator.cs b/src/TypeCache/Mediation/IMediator.cs index 685f2c91..fc428e82 100644 --- a/src/TypeCache/Mediation/IMediator.cs +++ b/src/TypeCache/Mediation/IMediator.cs @@ -5,25 +5,46 @@ namespace TypeCache.Mediation; public interface IMediator { /// - /// Execute a rule with no response. - /// Must register a rule of type . - /// May register rules of type that run in parallel after the rules run. + /// Execute a rule with no response.
+ /// Must register a rule of type .
+ /// May register rules of type that run in parallel before the rule runs.
+ /// May register rules of type that run in parallel after the rule runs. ///
/// Task Execute(REQUEST request, CancellationToken token = default) where REQUEST : IRequest; /// - /// Execute a rule with a response of type . - /// Must register a rule of type . - /// May register rules of type that run in parallel after the rules run. + /// Execute a named rule with no response.
+ /// Must register a named rule of type .
+ /// May register named and/or unamed rules of type that run in parallel before the rule runs.
+ /// May register named and/or unamed rules of type that run in parallel after the rule runs. + ///
+ /// + Task Execute(string name, REQUEST request, CancellationToken token = default) + where REQUEST : IRequest; + + /// + /// Execute a rule with a response of type .
+ /// Must register a rule of type .
+ /// May register rules of type that run in parallel before the rule runs.
+ /// May register rules of type that run in parallel after the rule runs. ///
/// Task Map(IRequest request, CancellationToken token = default); /// - /// Retrieve validation messages for a rule request. - /// Must register validation rules of type . + /// Execute a named rule with a response of type .
+ /// Must register a named rule of type .
+ /// May register named and/or unamed rules of type that run in parallel before the rule runs.
+ /// May register named and/or unamed rules of type that run in parallel after the rule runs. + ///
+ /// + Task Map(string name, IRequest request, CancellationToken token = default); + + /// + /// Validates a rule request. If no exception is thrown, the request is deemed valid.
+ /// Must register validation rules of type . ///
/// void Validate(REQUEST request, CancellationToken token = default) diff --git a/src/TypeCache/Mediation/Mediator.cs b/src/TypeCache/Mediation/Mediator.cs index 79f54680..595dd4b8 100644 --- a/src/TypeCache/Mediation/Mediator.cs +++ b/src/TypeCache/Mediation/Mediator.cs @@ -6,23 +6,99 @@ namespace TypeCache.Mediation; -internal sealed class Mediator(IServiceProvider serviceProvider, ILogger logger) +internal sealed class Mediator(IServiceProvider serviceProvider, ILogger? logger = null) : IMediator { - private readonly IServiceProvider _ServiceProvider = serviceProvider ?? serviceProvider.ThrowArgumentNullException(); - public async Task Execute(REQUEST request, CancellationToken token = default) where REQUEST : IRequest { - serviceProvider.AssertNotNull(); - - await using var scope = this._ServiceProvider.CreateAsyncScope(); + await using var scope = serviceProvider.CreateAsyncScope(); - this.Validate(request, token); + var validationRules = scope.ServiceProvider.GetServices>(); + if (validationRules.Any()) + this.ExecuteRules(request, validationRules, token); var rule = scope.ServiceProvider.GetRequiredService>(); - var afterRules = scope.ServiceProvider.GetService>>(); + var afterRules = scope.ServiceProvider.GetServices>(); + + await this.ExecuteRules(request, rule, afterRules, token); + } + + public async Task Execute(string name, REQUEST request, CancellationToken token = default) + where REQUEST : IRequest + { + await using var scope = serviceProvider.CreateAsyncScope(); + + var validationRules = scope.ServiceProvider.GetServices>(); + if (validationRules.Any()) + this.ExecuteRules(request, validationRules, token); + + var rule = scope.ServiceProvider.GetRequiredKeyedService>(name); + var afterRules = scope.ServiceProvider.GetServices>() + .Concat(scope.ServiceProvider.GetKeyedServices>(name)); + + await this.ExecuteRules(request, rule, afterRules, token); + } + + public Task Map(IRequest request, CancellationToken token = default) + => (Task)this.GetType().InvokeMethod(nameof(Mediator._Map), [request.GetType(), typeof(RESPONSE)], this, [request, token])!; + + public Task Map(string name, IRequest request, CancellationToken token = default) + => (Task)this.GetType().InvokeMethod(nameof(Mediator._Map), [request.GetType(), typeof(RESPONSE)], this, [name, request, token])!; + + public void Validate(REQUEST request, CancellationToken token = default) + where REQUEST : notnull + { + using var scope = serviceProvider.CreateScope(); + + var validationRules = scope.ServiceProvider.GetServices>(); + if (validationRules.Any()) + this.ExecuteRules(request, validationRules, token); + } + + public void Validate(string name, REQUEST request, CancellationToken token = default) + where REQUEST : notnull + { + using var scope = serviceProvider.CreateScope(); + + var validationRules = scope.ServiceProvider.GetServices>() + .Concat(scope.ServiceProvider.GetKeyedServices>(name)); + if (validationRules.Any()) + this.ExecuteRules(request, validationRules, token); + } + + private void ExecuteRules( + REQUEST request + , IEnumerable> rules + , CancellationToken token) + where REQUEST : notnull + { + try + { + Task.WaitAll(rules.Select(rule => rule.Validate(request, token)).ToArray(), token); + } + catch (AggregateException error) + { + logger?.LogAggregateException(error, "{mediator}.{function} aggregate failure.", [nameof(Mediator), nameof(Mediator.Validate)]); + if (error.InnerExceptions.Count == 1) + throw error.InnerException!; + + throw; + } + catch (Exception error) + { + logger?.LogError(error, "{mediator}.{function} failure: {message}", [nameof(Mediator), nameof(Mediator.Validate), error.Message]); + throw; + } + } + private async Task ExecuteRules( + REQUEST request + , IRule rule + , IEnumerable> afterRules + , CancellationToken token) + where REQUEST : IRequest + { try { await rule.Execute(request, token); @@ -41,22 +117,17 @@ public async Task Execute(REQUEST request, CancellationToken token = de } } - public async Task Map(REQUEST request, CancellationToken token = default) + private async Task ExecuteRules( + REQUEST request + , IRule rule + , IEnumerable> afterRules + , CancellationToken token) where REQUEST : IRequest { - serviceProvider.AssertNotNull(); - - await using var scope = this._ServiceProvider.CreateAsyncScope(); - - this.Validate(request, token); - - var rule = scope.ServiceProvider.GetRequiredService>(); - var afterRules = scope.ServiceProvider.GetService>>(); - try { var response = await rule.Map(request, token); - if (afterRules?.Any() is true) + if (afterRules.Any()) Task.WaitAll(afterRules.Select(afterRule => afterRule.Handle(request, response, token)).ToArray(), token); return response; @@ -75,36 +146,35 @@ public async Task Map(REQUEST request, Cancellation return default!; } - public Task Map(IRequest request, CancellationToken token = default) - => (Task)this.GetType().InvokeMethod(nameof(Mediator.Map), [request.GetType(), typeof(RESPONSE)], this, [request, token])!; - - public void Validate(REQUEST request, CancellationToken token = default) - where REQUEST : notnull + private async Task _Map(REQUEST request, CancellationToken token = default) + where REQUEST : IRequest { - serviceProvider.AssertNotNull(); + await using var scope = serviceProvider.CreateAsyncScope(); - using var scope = this._ServiceProvider.CreateScope(); + var validationRules = scope.ServiceProvider.GetServices>(); + if (validationRules.Any()) + this.ExecuteRules(request, validationRules, token); - var validationRules = scope.ServiceProvider.GetService>>(); - if (validationRules?.Any() is not true) - return; + var rule = scope.ServiceProvider.GetRequiredService>(); + var afterRules = scope.ServiceProvider.GetServices>(); - try - { - Task.WaitAll(validationRules!.Select(validationRule => validationRule.Validate(request, token)).ToArray(), token); - } - catch (AggregateException error) - { - logger?.LogAggregateException(error, "{mediator}.{function} aggregate failure.", [nameof(Mediator), nameof(Mediator.Validate)]); - if (error.InnerExceptions.Count == 1) - throw error.InnerException!; + return await this.ExecuteRules(request, rule, afterRules, token); + } - throw; - } - catch (Exception error) - { - logger?.LogError(error, "{mediator}.{function} failure: {message}", [nameof(Mediator), nameof(Mediator.Validate), error.Message]); - throw; - } + private async Task _Map(string name, REQUEST request, CancellationToken token = default) + where REQUEST : IRequest + { + await using var scope = serviceProvider.CreateAsyncScope(); + + var validationRules = scope.ServiceProvider.GetServices>() + .Concat(scope.ServiceProvider.GetKeyedServices>(name)); + if (validationRules.Any()) + this.ExecuteRules(request, validationRules, token); + + var rule = scope.ServiceProvider.GetRequiredKeyedService>(name); + var afterRules = scope.ServiceProvider.GetServices>() + .Concat(scope.ServiceProvider.GetKeyedServices>(name)); + + return await this.ExecuteRules(request, rule, afterRules, token); } } diff --git a/src/TypeCache/Mediation/RulesBuilder.cs b/src/TypeCache/Mediation/RulesBuilder.cs deleted file mode 100644 index 3679b793..00000000 --- a/src/TypeCache/Mediation/RulesBuilder.cs +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright (c) 2021 Samuel Abraham - -using Microsoft.Extensions.DependencyInjection; -using TypeCache.Extensions; - -namespace TypeCache.Mediation; - -public readonly struct RulesBuilder(IServiceCollection services) -{ - private readonly IServiceCollection _Services = services ?? services.ThrowArgumentNullException(); - - /// - /// Registers Singleton: . - /// - /// - public RulesBuilder AddAfterRule(IAfterRule afterRule) - where REQUEST : IRequest - { - afterRule.AssertNotNull(); - - this._Services.AddSingleton>(afterRule); - return this; - } - - /// - /// Registers Singleton: . - /// - /// - public RulesBuilder AddAfterRule(IAfterRule afterRule) - where REQUEST : IRequest - { - afterRule.AssertNotNull(); - - this._Services.AddSingleton>(afterRule); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddAfterRule(ServiceLifetime serviceLifetime, Func> createAfterRule) - where REQUEST : IRequest - { - createAfterRule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IAfterRule), createAfterRule, serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddAfterRule(ServiceLifetime serviceLifetime, Func afterRule) - where REQUEST : IRequest - { - afterRule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IAfterRule), provider => RuleFactory.CreateAfterRule(afterRule), serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddAfterRule(ServiceLifetime serviceLifetime, Action afterRule) - where REQUEST : IRequest - { - afterRule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IAfterRule), provider => RuleFactory.CreateAfterRule(afterRule), serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddAfterRule(ServiceLifetime serviceLifetime, Func> createAfterRule) - where REQUEST : IRequest - { - createAfterRule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IAfterRule), createAfterRule, serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddAfterRule(ServiceLifetime serviceLifetime, Func afterRule) - where REQUEST : IRequest - { - afterRule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IAfterRule), provider => RuleFactory.CreateAfterRule(afterRule), serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddAfterRule(ServiceLifetime serviceLifetime, Action afterRule) - where REQUEST : IRequest - { - afterRule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IAfterRule), provider => RuleFactory.CreateAfterRule(afterRule), serviceLifetime)); - return this; - } - - /// - /// Registers Singleton: . - /// - /// - public RulesBuilder AddRule(IRule rule) - where REQUEST : IRequest - { - rule.AssertNotNull(); - - this._Services.AddSingleton>(rule); - return this; - } - - /// - /// Registers Singleton: . - /// - /// - public RulesBuilder AddRule(IRule rule) - where REQUEST : IRequest - { - rule.AssertNotNull(); - - this._Services.AddSingleton>(rule); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddRule(ServiceLifetime serviceLifetime, Func> createRule) - where REQUEST : IRequest - { - createRule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IRule), createRule, serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddRule(ServiceLifetime serviceLifetime, Func rule) - where REQUEST : IRequest - { - rule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IRule), provider => RuleFactory.CreateRule(rule), serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddRule(ServiceLifetime serviceLifetime, Action rule) - where REQUEST : IRequest - { - rule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IRule), provider => RuleFactory.CreateRule(rule), serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddRule(ServiceLifetime serviceLifetime, Func> createRule) - where REQUEST : IRequest - { - createRule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IRule), createRule, serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddRule(ServiceLifetime serviceLifetime, Func> rule) - where REQUEST : IRequest - { - rule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IRule), provider => RuleFactory.CreateRule(rule), serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddRule(ServiceLifetime serviceLifetime, Func rule) - where REQUEST : IRequest - { - rule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IRule), provider => RuleFactory.CreateRule(rule), serviceLifetime)); - return this; - } - - /// - /// Registers Singleton: . - /// - /// - public RulesBuilder AddValidationRule(IValidationRule validationRule) - where REQUEST : IRequest - { - validationRule.AssertNotNull(); - - this._Services.AddSingleton>(validationRule); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddValidationRule(ServiceLifetime serviceLifetime, Func validationRule) - where REQUEST : IRequest - { - validationRule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IValidationRule), provider => RuleFactory.CreateValidationRule(validationRule), serviceLifetime)); - return this; - } - - /// - /// Registers an implementation of . - /// - /// - public RulesBuilder AddValidationRule(ServiceLifetime serviceLifetime, Action validationRule) - where REQUEST : IRequest - { - validationRule.AssertNotNull(); - - this._Services.Add(ServiceDescriptor.Describe(typeof(IValidationRule), provider => RuleFactory.CreateValidationRule(validationRule), serviceLifetime)); - return this; - } -} diff --git a/src/TypeCache/TypeCache.csproj b/src/TypeCache/TypeCache.csproj index c8e985ed..ea24e4f6 100644 --- a/src/TypeCache/TypeCache.csproj +++ b/src/TypeCache/TypeCache.csproj @@ -6,7 +6,7 @@ TypeCache true TypeCache - 8.1.3 + 8.2.0 Samuel Abraham <sam987883@gmail.com> Samuel Abraham <sam987883@gmail.com> TypeCache Reflection diff --git a/src/TypeCache/Utilities/RegexCache.cs b/src/TypeCache/Utilities/RegexCache.cs deleted file mode 100644 index cd0cb497..00000000 --- a/src/TypeCache/Utilities/RegexCache.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2021 Samuel Abraham - -using System.Text.RegularExpressions; -using TypeCache.Collections; -using static System.Text.RegularExpressions.RegexOptions; - -namespace TypeCache.Utilities; - -public static class RegexCache -{ - private static readonly TimeSpan DefaultMatchTimeout = TimeSpan.FromMinutes(1); - - private static readonly IReadOnlyDictionary<(string Pattern, RegexOptions Options), Regex> Cache = - new LazyDictionary<(string Pattern, RegexOptions Options), Regex>(_ => new(_.Pattern, _.Options, DefaultMatchTimeout)); - - [MethodImpl(AggressiveInlining), DebuggerHidden] - public static Regex Regex([StringSyntax(StringSyntaxAttribute.Regex)] this string @this, RegexOptions options = Compiled | CultureInvariant | Singleline) - => Cache[(@this, options)]; -} diff --git a/src/TypeCache/Utilities/ValueConverter.cs b/src/TypeCache/Utilities/ValueConverter.cs index bd768569..5204adad 100644 --- a/src/TypeCache/Utilities/ValueConverter.cs +++ b/src/TypeCache/Utilities/ValueConverter.cs @@ -80,7 +80,7 @@ public static Expression CreateConversionExpression(this Expression @this, Type (ScalarType.DateOnly, ScalarType.String) => LambdaFactory.CreateFunc((DateOnly _) => _.ToISO8601(null)), (ScalarType.DateTime, ScalarType.String) => LambdaFactory.CreateFunc((DateTime _) => _.ToISO8601(null)), (ScalarType.DateTimeOffset, ScalarType.String) => LambdaFactory.CreateFunc((DateTimeOffset _) => _.ToISO8601(null)), - (ScalarType.Enum, ScalarType.String) => LambdaFactory.CreateFunc((Enum _) => _.ToString("F")), + (ScalarType.Enum, ScalarType.String) => LambdaFactory.CreateFunc((Enum _) => _.Name()), (ScalarType.TimeOnly, ScalarType.String) => LambdaFactory.CreateFunc((TimeOnly _) => _.ToISO8601(null)), (ScalarType.TimeSpan, ScalarType.String) => LambdaFactory.CreateFunc((TimeSpan _) => _.ToText(null)), (ScalarType.Guid, ScalarType.String) => LambdaFactory.CreateFunc((Guid _) => _.ToString("D")), @@ -167,200 +167,230 @@ public static Expression CreateConversionExpression(this Expression @this, Type return expression; } - public static bool? ConvertToBoolean(object? value) => value switch - { - string text when text.IsNotBlank() => bool.Parse(text), - null or string => null, - char x when TRUE_CHARS.Contains(x) => true, - (sbyte)0 or (short)0 or 0 or 0L or (byte)0 or (ushort)0 or 0U or 0UL or 0F or 0D or 0M => false, - sbyte or short or int or long or byte or ushort or uint or ulong or float or double or decimal => true, - Int128 x => x != Int128.Zero, - BigInteger x => x != BigInteger.Zero, - UInt128 x => x != UInt128.Zero, - IntPtr x => x != IntPtr.Zero, - UIntPtr x => x != UIntPtr.Zero, - Half x => x != (Half)0, - DateOnly x => x != DateOnly.MinValue, - DateTime x => x != DateTime.MinValue, - DateTimeOffset x => x != DateTimeOffset.MinValue, - TimeOnly x => x != TimeOnly.MinValue, - TimeSpan x => x != TimeSpan.MinValue, - Guid x => x != Guid.Empty, - _ => (bool)value - }; + public static bool? ConvertToBoolean(object? value) + => value switch + { + string text when text.IsNotBlank() => text.Parse(), + null or string => null, + char x when TRUE_CHARS.Contains(x) => true, + (sbyte)0 or (short)0 or 0 or 0L or (byte)0 or (ushort)0 or 0U or 0UL or 0F or 0D or 0M => false, + sbyte or short or int or long or byte or ushort or uint or ulong or float or double or decimal => true, + Int128 x => x != Int128.Zero, + BigInteger x => x != BigInteger.Zero, + UInt128 x => x != UInt128.Zero, + IntPtr x => x != IntPtr.Zero, + UIntPtr x => x != UIntPtr.Zero, + Half x => x != (Half)0, + DateOnly x => x != DateOnly.MinValue, + DateTime x => x != DateTime.MinValue, + DateTimeOffset x => x != DateTimeOffset.MinValue, + TimeOnly x => x != TimeOnly.MinValue, + TimeSpan x => x != TimeSpan.MinValue, + Guid x => x != Guid.Empty, + _ => (bool)value + }; [MethodImpl(AggressiveInlining), DebuggerHidden] - public static BigInteger? ConvertToBigInteger(object? value) => value.ConvertToPrimitive(BigInteger.Zero, BigInteger.One, BigInteger.Parse, x => (BigInteger)x); + public static BigInteger? ConvertToBigInteger(object? value) + => value.ConvertToPrimitive(BigInteger.Zero, BigInteger.One, BigInteger.Parse, x => (BigInteger)x); [MethodImpl(AggressiveInlining), DebuggerHidden] - public static byte? ConvertToByte(object? value) => value.ConvertToPrimitive((byte)0, (byte)1, byte.Parse, Convert.ToByte); + public static byte? ConvertToByte(object? value) + => value.ConvertToPrimitive((byte)0, (byte)1, byte.Parse, Convert.ToByte); [MethodImpl(AggressiveInlining), DebuggerHidden] - public static char? ConvertToChar(object? value) => value.ConvertToPrimitive('0', '1', Convert.ToChar, Convert.ToChar); + public static char? ConvertToChar(object? value) + => value.ConvertToPrimitive('0', '1', Convert.ToChar, Convert.ToChar); - public static DateOnly? ConvertToDateOnly(object? value) => value switch - { - string text when text.IsNotBlank() => DateOnly.Parse(text), - null or string => null, - int x => DateOnly.FromDayNumber(x), - uint x => DateOnly.FromDayNumber(checked((int)x)), - DateTime x => x.ToDateOnly(), - DateTimeOffset x => x.ToDateOnly(), - _ => (DateOnly)value - }; - - public static DateTime? ConvertToDateTime(object? value) => value switch - { - string text when text.IsNotBlank() => DateTime.Parse(text), - null or string => null, - long x => new(x), - ulong x => new(checked((long)x)), - DateOnly x => x.ToDateTime(TimeOnly.MinValue), - DateTimeOffset x => x.DateTime, - _ => (DateTime)value - }; - - public static DateTimeOffset? ConvertToDateTimeOffset(object? value) => value switch - { - string text when text.IsNotBlank() => DateTimeOffset.Parse(text), - null or string => null, - long x => new DateTime(x).ToDateTimeOffset(), - ulong x => new DateTime(checked((long)x)).ToDateTimeOffset(), - DateOnly x => x.ToDateTime(TimeOnly.MinValue).ToDateTimeOffset(), - DateTime x => x.ToDateTimeOffset(), - _ => (DateTimeOffset)value - }; + public static DateOnly? ConvertToDateOnly(object? value) + => value switch + { + string text when text.IsNotBlank() => DateOnly.Parse(text), + null or string => null, + int x => DateOnly.FromDayNumber(x), + uint x => DateOnly.FromDayNumber(checked((int)x)), + DateTime x => x.ToDateOnly(), + DateTimeOffset x => x.ToDateOnly(), + _ => (DateOnly)value + }; + + public static DateTime? ConvertToDateTime(object? value) + => value switch + { + string text when text.IsNotBlank() => DateTime.Parse(text), + null or string => null, + long x => new(x), + ulong x => new(checked((long)x)), + DateOnly x => x.ToDateTime(TimeOnly.MinValue), + DateTimeOffset x => x.DateTime, + _ => (DateTime)value + }; + + public static DateTimeOffset? ConvertToDateTimeOffset(object? value) + => value switch + { + string text when text.IsNotBlank() => DateTimeOffset.Parse(text), + null or string => null, + long x => new DateTime(x).ToDateTimeOffset(), + ulong x => new DateTime(checked((long)x)).ToDateTimeOffset(), + DateOnly x => x.ToDateTime(TimeOnly.MinValue).ToDateTimeOffset(), + DateTime x => x.ToDateTimeOffset(), + _ => (DateTimeOffset)value + }; [MethodImpl(AggressiveInlining), DebuggerHidden] - public static decimal? ConvertToDecimal(object? value) => value.ConvertToPrimitive(0M, 1M, decimal.Parse, Convert.ToDecimal); + public static decimal? ConvertToDecimal(object? value) + => value.ConvertToPrimitive(0M, 1M, decimal.Parse, Convert.ToDecimal); [MethodImpl(AggressiveInlining), DebuggerHidden] - public static double? ConvertToDouble(object? value) => value.ConvertToPrimitive(0D, 1D, double.Parse, Convert.ToDouble); + public static double? ConvertToDouble(object? value) + => value.ConvertToPrimitive(0D, 1D, double.Parse, Convert.ToDouble); - public static T? ConvertToEnum(object? value) where T : struct, Enum => value switch - { - string text when text.IsNotBlank() => Enum.Parse(text, true), - null or string => null, - sbyte or short or int or long or byte or ushort or uint or ulong => (T)Enum.ToObject(typeof(T), value), - _ => (T)value - }; + public static T? ConvertToEnum(object? value) where T : struct, Enum + => value switch + { + string text when text.IsNotBlank() => text.ToEnum(), + null or string => null, + sbyte or short or int or long or byte or ushort or uint or ulong => (T)Enum.ToObject(typeof(T), value), + _ => (T)value + }; - public static Guid? ConvertToGuid(object? value) => value switch - { - string text when text.IsNotBlank() => Guid.Parse(text), - null or string => null, - _ => (Guid)value - }; + public static Guid? ConvertToGuid(object? value) + => value switch + { + string text when text.IsNotBlank() => Guid.Parse(text), + null or string => null, + _ => (Guid)value + }; [MethodImpl(AggressiveInlining), DebuggerHidden] - public static Half? ConvertToHalf(object? value) => value.ConvertToPrimitive((Half)0, (Half)1, Half.Parse, x => (Half)x); + public static Half? ConvertToHalf(object? value) + => value.ConvertToPrimitive((Half)0, (Half)1, Half.Parse, x => (Half)x); - public static Index? ConvertToIndex(object? value) => value switch - { - string text when text.IsNotBlank() => new Index(int.Parse(text)), - null or string => null, - sbyte x => new Index(x), - short x => new Index(x), - int x => new Index(x), - long x => new Index(checked((int)x)), - byte x => new Index(x), - ushort x => new Index(x), - uint x => new Index(checked((int)x)), - ulong x => new Index(checked((int)x)), - _ => (Index)value - }; + public static Index? ConvertToIndex(object? value) + => value switch + { + string text when text.IsNotBlank() => new Index(text.Parse()), + null or string => null, + sbyte x => new Index(x), + short x => new Index(x), + int x => new Index(x), + long x => new Index(checked((int)x)), + byte x => new Index(x), + ushort x => new Index(x), + uint x => new Index(checked((int)x)), + ulong x => new Index(checked((int)x)), + _ => (Index)value + }; [MethodImpl(AggressiveInlining), DebuggerHidden] - public static short? ConvertToInt16(object? value) => value.ConvertToPrimitive((short)0, (short)1, short.Parse, Convert.ToInt16); + public static short? ConvertToInt16(object? value) + => value.ConvertToPrimitive((short)0, (short)1, short.Parse, Convert.ToInt16); [MethodImpl(AggressiveInlining), DebuggerHidden] - public static int? ConvertToInt32(object? value) => value.ConvertToPrimitive(0, 1, int.Parse, x => x); + public static int? ConvertToInt32(object? value) + => value.ConvertToPrimitive(0, 1, int.Parse, x => x); [MethodImpl(AggressiveInlining), DebuggerHidden] - public static long? ConvertToInt64(object? value) => value.ConvertToPrimitive((long)0, (long)1, long.Parse, Convert.ToInt64); + public static long? ConvertToInt64(object? value) + => value.ConvertToPrimitive((long)0, (long)1, long.Parse, Convert.ToInt64); [MethodImpl(AggressiveInlining), DebuggerHidden] - public static Int128? ConvertToInt128(object? value) => value.ConvertToPrimitive(Int128.Zero, Int128.One, Int128.Parse, x => (Int128)x); + public static Int128? ConvertToInt128(object? value) + => value.ConvertToPrimitive(Int128.Zero, Int128.One, Int128.Parse, x => (Int128)x); - public static nint? ConvertToIntPtr(object? value) => value switch - { - string text when text.IsNotBlank() => IntPtr.Parse(text), - null or string => null, - _ => (nint)value - }; + public static nint? ConvertToIntPtr(object? value) + => value switch + { + string text when text.IsNotBlank() => IntPtr.Parse(text), + null or string => null, + _ => (nint)value + }; [MethodImpl(AggressiveInlining), DebuggerHidden] - public static sbyte? ConvertToSByte(object? value) => value.ConvertToPrimitive((sbyte)0, (sbyte)1, sbyte.Parse, Convert.ToSByte); + public static sbyte? ConvertToSByte(object? value) + => value.ConvertToPrimitive((sbyte)0, (sbyte)1, sbyte.Parse, Convert.ToSByte); [MethodImpl(AggressiveInlining), DebuggerHidden] - public static float? ConvertToSingle(object? value) => value.ConvertToPrimitive(0F, 1F, float.Parse, Convert.ToSingle); + public static float? ConvertToSingle(object? value) + => value.ConvertToPrimitive(0F, 1F, float.Parse, Convert.ToSingle); - public static string? ConvertToString(object? value) => value switch - { - null => null, - DateOnly x => x.ToISO8601(), - DateTime x => x.ToISO8601(), - DateTimeOffset x => x.ToISO8601(), - Enum x => x.ToString("F"), - TimeOnly x => x.ToISO8601(), - TimeSpan x => x.ToText(), - _ => value.ToString(), - }; - - public static TimeOnly? ConvertToTimeOnly(object? value) => value switch - { - string text when text.IsNotBlank() => TimeOnly.Parse(text), - null or string => null, - long x => new(x), - ulong x => new(checked((long)x)), - DateTime x => TimeOnly.FromDateTime(x), - DateTimeOffset x => TimeOnly.FromDateTime(x.DateTime), - TimeSpan x => TimeOnly.FromTimeSpan(x), - _ => (TimeOnly)value - }; - - public static TimeSpan? ConvertToTimeSpan(object? value) => value switch - { - string text when text.IsNotBlank() => TimeSpan.Parse(text), - null or string => null, - long x => new(x), - ulong x => new(checked((long)x)), - _ => (TimeSpan)value - }; + public static string? ConvertToString(object? value) + => value switch + { + null => null, + DateOnly x => x.ToISO8601(), + DateTime x => x.ToISO8601(), + DateTimeOffset x => x.ToISO8601(), + Enum x => x.Name(), + TimeOnly x => x.ToISO8601(), + TimeSpan x => x.ToText(), + _ => value.ToString(), + }; + + public static TimeOnly? ConvertToTimeOnly(object? value) + => value switch + { + string text when text.IsNotBlank() => TimeOnly.Parse(text), + null or string => null, + long x => new(x), + ulong x => new(checked((long)x)), + DateTime x => TimeOnly.FromDateTime(x), + DateTimeOffset x => TimeOnly.FromDateTime(x.DateTime), + TimeSpan x => TimeOnly.FromTimeSpan(x), + _ => (TimeOnly)value + }; + + public static TimeSpan? ConvertToTimeSpan(object? value) + => value switch + { + string text when text.IsNotBlank() => TimeSpan.Parse(text), + null or string => null, + long x => new(x), + ulong x => new(checked((long)x)), + _ => (TimeSpan)value + }; [MethodImpl(AggressiveInlining), DebuggerHidden] - public static ushort? ConvertToUInt16(object? value) => value.ConvertToPrimitive((ushort)0, (ushort)1, ushort.Parse, Convert.ToUInt16); + public static ushort? ConvertToUInt16(object? value) + => value.ConvertToPrimitive((ushort)0, (ushort)1, ushort.Parse, Convert.ToUInt16); [MethodImpl(AggressiveInlining), DebuggerHidden] - public static uint? ConvertToUInt32(object? value) => value.ConvertToPrimitive(0U, 1U, uint.Parse, Convert.ToUInt32); + public static uint? ConvertToUInt32(object? value) + => value.ConvertToPrimitive(0U, 1U, uint.Parse, Convert.ToUInt32); [MethodImpl(AggressiveInlining), DebuggerHidden] - public static ulong? ConvertToUInt64(object? value) => value.ConvertToPrimitive((ulong)0, (ulong)1, ulong.Parse, Convert.ToUInt64); + public static ulong? ConvertToUInt64(object? value) + => value.ConvertToPrimitive((ulong)0, (ulong)1, ulong.Parse, Convert.ToUInt64); [MethodImpl(AggressiveInlining), DebuggerHidden] - public static UInt128? ConvertToUInt128(object? value) => value.ConvertToPrimitive(UInt128.Zero, UInt128.One, UInt128.Parse, x => (UInt128)x); + public static UInt128? ConvertToUInt128(object? value) + => value.ConvertToPrimitive(UInt128.Zero, UInt128.One, UInt128.Parse, _ => (UInt128)_); - public static nuint? ConvertToUIntPtr(object? value) => value switch - { - string text when text.IsNotBlank() => UIntPtr.Parse(text), - null or string => null, - _ => (nuint)value - }; + public static nuint? ConvertToUIntPtr(object? value) + => value switch + { + string text when text.IsNotBlank() => UIntPtr.Parse(text), + null or string => null, + _ => (nuint)value + }; - public static Uri? ConvertToUri(object? value) => value switch - { - string text when text.IsNotBlank() => new Uri(text, text[0] is '/' ? UriKind.Relative : UriKind.Absolute), - null or string => null, - _ => (Uri)value - }; + public static Uri? ConvertToUri(object? value) + => value switch + { + string text when text.IsNotBlank() => new Uri(text, text[0] is '/' ? UriKind.Relative : UriKind.Absolute), + null or string => null, + _ => (Uri)value + }; - private static T? ConvertToPrimitive(this object? @this, T trueValue, T falseValue, Func parse, Func fromInt) where T : struct => @this switch - { - string text when text.IsNotBlank() => parse(text), - null or string => null, - true => trueValue, - false => falseValue, - Index x => fromInt(x.Value), - _ => (T)@this - }; + private static T? ConvertToPrimitive(this object? @this, T trueValue, T falseValue, Func parse, Func fromInt) where T : struct + => @this switch + { + string text when text.IsNotBlank() => parse(text), + null or string => null, + true => trueValue, + false => falseValue, + Index x => fromInt(x.Value), + _ => (T)@this + }; } diff --git a/tests/TypeCache.GraphQL.TestApp/Program.cs b/tests/TypeCache.GraphQL.TestApp/Program.cs index d61aad78..fbbd6e91 100644 --- a/tests/TypeCache.GraphQL.TestApp/Program.cs +++ b/tests/TypeCache.GraphQL.TestApp/Program.cs @@ -14,7 +14,8 @@ var builder = WebApplication.CreateBuilder(args); builder.Services - .AddMediation(builder => builder.AddSqlCommandRules()) + .AddMediation() + .AddSqlCommandRules() .AddHashMaker((decimal)(Tau - E), (decimal)(Tau + 2 * E)) .AddDataSource(DATASOURCE, SqlClientFactory.Instance, builder.Configuration.GetConnectionString(DATASOURCE)!, ["AdventureWorks2019"]) .AddGraphQL() diff --git a/tests/TypeCache.Tests/Data/Extensions/SqlExtensions.cs b/tests/TypeCache.Tests/Data/Extensions/SqlExtensions.cs index 7a65968b..6d55f7e0 100644 --- a/tests/TypeCache.Tests/Data/Extensions/SqlExtensions.cs +++ b/tests/TypeCache.Tests/Data/Extensions/SqlExtensions.cs @@ -1,7 +1,10 @@ // Copyright (c) 2021 Samuel Abraham +using System; +using System.Collections.Generic; using NSubstitute; using NSubstitute.Extensions; +using TypeCache.Collections; using TypeCache.Data; using TypeCache.Data.Extensions; using Xunit; @@ -27,11 +30,10 @@ public class Person public void CreateCountSQL() { var dataSource = CreateDataSourceMock(SqlServer); - var table = new DatabaseObject("db.dbo.Test"); - var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, table, "db", "dbo", "Test"); + var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, "db", "dbo", "Test", null, null); var expected = Invariant($@"SELECT COUNT(*) -FROM {table} WITH(NOLOCK) +FROM {objectSchema} WITH(NOLOCK) WHERE [First Name] = N'Sarah' AND [Last_Name] = N'Marshal'; "); var actual = objectSchema.CreateCountSQL(null, "[First Name] = N'Sarah' AND [Last_Name] = N'Marshal'"); @@ -43,15 +45,14 @@ public void CreateCountSQL() public void CreateDeleteBatchSQL() { var dataSource = CreateDataSourceMock(SqlServer); - var table = new DatabaseObject("db.dbo.Test"); - var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, table, "db", "dbo", "Test", + var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, "db", "dbo", "Test", [ new ColumnSchema("ID", false, true, true, true, typeof(int).TypeHandle), new ColumnSchema("First Name", false, false, false, false, typeof(string).TypeHandle), new ColumnSchema("Last Name", false, false, false, false, typeof(string).TypeHandle), - ]); + ], null); - var expected = Invariant($@"DELETE {table} + var expected = Invariant($@"DELETE {objectSchema} OUTPUT INSERTED.[First Name] AS [First Name], DELETED.[Last_Name] AS [Last_Name], INSERTED.ID WHERE [ID] IN (1, 2, 3); "); @@ -65,15 +66,14 @@ public void CreateDeleteBatchSQL() public void CreateDeleteSQL() { var dataSource = CreateDataSourceMock(SqlServer); - var table = new DatabaseObject("db.dbo.Test"); - var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, table, "db", "dbo", "Test", + var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, "db", "dbo", "Test", [ new ColumnSchema("ID", false, true, true, true, typeof(int).TypeHandle), new ColumnSchema("First Name", false, false, false, false, typeof(string).TypeHandle), new ColumnSchema("Last Name", false, false, false, false, typeof(string).TypeHandle), - ]); + ], null); - var expected = Invariant($@"DELETE {table} + var expected = Invariant($@"DELETE {objectSchema} OUTPUT DELETED.[ID], DELETED.[First Name], DELETED.[Last_Name] WHERE [First Name] = N'Sarah' AND [Last_Name] = N'Marshal'; "); @@ -86,13 +86,12 @@ public void CreateDeleteSQL() public void CreateBatchInsertSQL() { var dataSource = CreateDataSourceMock(SqlServer); - var table = new DatabaseObject("db.dbo.Test"); - var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, table, "db", "dbo", "Test", + var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, "db", "dbo", "Test", [ new ColumnSchema("ID", false, true, true, true, typeof(int).TypeHandle), new ColumnSchema("FirstName", false, false, false, false, typeof(string).TypeHandle), new ColumnSchema("LastName", false, false, false, false, typeof(string).TypeHandle), - ]); + ], null); Person[] data = [ new() { ID = 1, FirstName = "FirstName1", LastName = "LastName1", Age = 30 }, @@ -100,7 +99,7 @@ public void CreateBatchInsertSQL() new() { ID = 3, FirstName = "FirstName3", LastName = "LastName3", Age = 32 } ]; - var expected = Invariant($@"INSERT INTO {table} + var expected = Invariant($@"INSERT INTO {objectSchema} ([FirstName], [LastName]) OUTPUT INSERTED.ID, INSERTED.[LastName] VALUES (N'FirstName1', N'LastName1') @@ -116,13 +115,12 @@ public void CreateBatchInsertSQL() public void CreateInsertSQL() { var dataSource = CreateDataSourceMock(SqlServer); - var table = new DatabaseObject("db.dbo.Test"); - var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, table, "db", "dbo", "Test", + var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, "db", "dbo", "Test", [ new ColumnSchema("ID", false, true, true, true, typeof(int).TypeHandle), new ColumnSchema("First Name", false, false, false, false, typeof(string).TypeHandle), new ColumnSchema("Last Name", false, false, false, false, typeof(string).TypeHandle), - ]); + ], null); var selectQuery = new SelectQuery { From = new("[dbo].[NonCustomers]"), @@ -133,7 +131,7 @@ public void CreateInsertSQL() Where = "[First Name] = N'Sarah' AND [Last_Name] = N'Marshal'", }; - var expected = Invariant($@"INSERT INTO {table} + var expected = Invariant($@"INSERT INTO {objectSchema} ([[First Name]]], [[Last_Name]]], [Age], [Amount]) OUTPUT INSERTED.[First Name] AS [First Name], INSERTED.[ID] AS [ID] SELECT ID, TRIM([First Name]) AS [First Name], UPPER([LastName]) AS LastName, 40 Age, Amount AS Amount @@ -151,12 +149,12 @@ HAVING MAX([Age]) > 40 public void CreateSelectSQL() { var dataSource = CreateDataSourceMock(SqlServer); - var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, new DatabaseObject("db.dbo.Test"), "db", "dbo", "Test", + var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, "db", "dbo", "Test", [ new ColumnSchema("ID", false, true, true, true, typeof(int).TypeHandle), new ColumnSchema("FirstName", false, false, false, false, typeof(string).TypeHandle), new ColumnSchema("LastName", false, false, false, false, typeof(string).TypeHandle), - ]); + ], null); var selectQuery = new SelectQuery { Select = ["ID", "TRIM([FirstName]) AS [FirstName]", "UPPER([LastName]) AS LastName", "40 Age", "Amount AS Amount"], @@ -185,13 +183,12 @@ HAVING MAX([Age]) > 40 public void CreateBatchUpdateSQL() { var dataSource = CreateDataSourceMock(SqlServer); - var table = new DatabaseObject("db.dbo.Test"); - var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, table, "db", "dbo", "Test", + var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, "db", "dbo", "Test", [ new ColumnSchema("ID", false, true, true, true, typeof(int).TypeHandle), new ColumnSchema("FirstName", false, false, false, false, typeof(string).TypeHandle), new ColumnSchema("LastName", false, false, false, false, typeof(string).TypeHandle), - ]); + ], null); Person[] data = [ new() { ID = 1, FirstName = "FirstName1", LastName = "LastName1", Age = 30 }, @@ -199,10 +196,10 @@ public void CreateBatchUpdateSQL() new() { ID = 3, FirstName = "FirstName3", LastName = "LastName3", Age = 32 } ]; - var expected = Invariant($@"UPDATE {table} WITH(UPDLOCK) + var expected = Invariant($@"UPDATE {objectSchema} WITH(UPDLOCK) SET [FirstName] = data.[FirstName], [LastName] = data.[LastName] OUTPUT INSERTED.[FirstName] AS [FirstName], DELETED.LastName AS LastName, INSERTED.[ID] AS [ID] -FROM {table} AS _ +FROM {objectSchema} AS _ INNER JOIN ( VALUES (N'FirstName1', N'LastName1') @@ -221,15 +218,14 @@ INNER JOIN public void CreateUpdateSQL() { var dataSource = CreateDataSourceMock(SqlServer); - var table = new DatabaseObject("db.dbo.Test"); - var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, table, "db", "dbo", "Test", + var objectSchema = new ObjectSchema(dataSource, DatabaseObjectType.Table, "db", "dbo", "Test", [ new ColumnSchema("ID", false, true, true, true, typeof(int).TypeHandle), new ColumnSchema("First Name", false, false, false, false, typeof(string).TypeHandle), new ColumnSchema("Last Name", false, false, false, false, typeof(string).TypeHandle), - ]); + ], null); - var expected = Invariant($@"UPDATE {table} WITH(UPDLOCK) + var expected = Invariant($@"UPDATE {objectSchema} WITH(UPDLOCK) SET [First Name] = N'Sarah', Last_Name = N'Marshal', Age = @Param1 OUTPUT INSERTED.[First Name] AS [First Name], DELETED.[Last_Name] AS [Last_Name], INSERTED.ID AS [ID] WHERE [First Name] = N'Sarah' AND [Last_Name] = N'Marshal'; diff --git a/tests/TypeCache.Tests/Extensions/StringExtensions.cs b/tests/TypeCache.Tests/Extensions/StringExtensions.cs index abe8dd51..8554bc55 100644 --- a/tests/TypeCache.Tests/Extensions/StringExtensions.cs +++ b/tests/TypeCache.Tests/Extensions/StringExtensions.cs @@ -31,14 +31,12 @@ public void ToBase64() public void Has() { Assert.True(TEST_STRING.Has("BCC 1")); - Assert.False(TEST_STRING.Has("BCC 1", StringComparison.Ordinal)); } [Fact] public void Is() { Assert.True(TEST_STRING.Is("AABBCC 123 `~!#$%^\t\r\n")); - Assert.False(TEST_STRING.Is("AABBCC 123 `~!#$%^\t\r\n", StringComparison.Ordinal)); } [Fact] @@ -76,7 +74,6 @@ public void Left() Assert.False(TEST_STRING.Left('a')); Assert.True(TEST_STRING.Left("AABBCC 123")); - Assert.False(TEST_STRING.Left("AABBCC 123", StringComparison.Ordinal)); } [Fact] @@ -112,23 +109,22 @@ public void Right() Assert.False("321 cCbBaA".Right('a')); Assert.True("321 cCbBaA".Right("ccbbaa")); - Assert.False("321 cCbBaA".Right("ccbbaa", StringComparison.Ordinal)); } [Fact] - public void Segment() + public void ToEnum() { - Assert.Equal(new StringSegment(TEST_STRING), TEST_STRING.Segment()); - Assert.Equal(new StringSegment(TEST_STRING, 2, 0), TEST_STRING.Segment(2, 0)); - Assert.Equal(new StringSegment(TEST_STRING, 2, 3), TEST_STRING.Segment(2, 3)); - Assert.Equal(new StringSegment(TEST_STRING, 9, 1), TEST_STRING.Segment(9, 1)); + Assert.Equal(StringComparison.Ordinal, nameof(StringComparison.Ordinal).ToEnum()); + Assert.Equal(StringComparison.OrdinalIgnoreCase, nameof(StringComparison.OrdinalIgnoreCase).ToUpperInvariant().ToEnum()); } [Fact] - public void ToEnum() + public void ToStringSegment() { - Assert.Equal(StringComparison.Ordinal, nameof(StringComparison.Ordinal).ToEnum()); - Assert.Equal(StringComparison.OrdinalIgnoreCase, nameof(StringComparison.OrdinalIgnoreCase).ToUpperInvariant().ToEnum()); + Assert.Equal(new StringSegment(TEST_STRING), TEST_STRING.ToStringSegment()); + Assert.Equal(new StringSegment(TEST_STRING, 2, 0), TEST_STRING.ToStringSegment(2, 0)); + Assert.Equal(new StringSegment(TEST_STRING, 2, 3), TEST_STRING.ToStringSegment(2, 3)); + Assert.Equal(new StringSegment(TEST_STRING, 9, 1), TEST_STRING.ToStringSegment(9, 1)); } [Fact]