From db3a989fbbc949a34b7359c243a59e2325ac5c06 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 18 Feb 2024 02:34:55 +0100 Subject: [PATCH 01/27] start --- docs/rules/DAP048.md | 26 +++++++ .../DapperAnalyzer.Diagnostics.cs | 1 + .../CodeAnalysis/DapperAnalyzer.cs | 73 +++++++++++-------- .../Internal/Inspection.cs | 10 +++ test/Dapper.AOT.Test/Verifiers/DAP048.cs | 53 ++++++++++++++ 5 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 docs/rules/DAP048.md create mode 100644 test/Dapper.AOT.Test/Verifiers/DAP048.cs diff --git a/docs/rules/DAP048.md b/docs/rules/DAP048.md new file mode 100644 index 00000000..6e66ec20 --- /dev/null +++ b/docs/rules/DAP048.md @@ -0,0 +1,26 @@ +# DAP048 + +[DbString](https://github.com/DapperLib/Dapper/blob/main/Dapper/DbString.cs) +achieves the same as [DbValueAttribute](https://github.com/DapperLib/DapperAOT/blob/main/src/Dapper.AOT/DbValueAttribute.cs) +on which a lot of features rely. + +// TODO +Bad: + +``` c# +public class MyType +{ + public string _firstName; + public string first_name; +} +``` + +Good: + +``` c# +public class MyType +{ + public string firstName; + public string firstNaming; +} +``` diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.Diagnostics.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.Diagnostics.cs index 8c5a5356..c9268f6a 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.Diagnostics.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.Diagnostics.cs @@ -55,6 +55,7 @@ public static readonly DiagnosticDescriptor CancellationDuplicated = LibraryWarning("DAP045", "Duplicate cancellation", "Multiple parameter values cannot define cancellation"), AmbiguousProperties = LibraryWarning("DAP046", "Ambiguous properties", "Properties have same name '{0}' after normalization and can be conflated"), AmbiguousFields = LibraryWarning("DAP047", "Ambiguous fields", "Fields have same name '{0}' after normalization and can be conflated"), + MoveFromDbString = LibraryWarning("DAP048", "Move from DbString to DbValue", "DbString achieves the same as [DbValue] does. Use it instead."), // SQL parse specific GeneralSqlError = SqlWarning("DAP200", "SQL error", "SQL error: {0}"), diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs index f66c60aa..ec58be92 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs @@ -191,7 +191,7 @@ private void ValidateDapperMethod(in OperationAnalysisContext ctx, IOperation sq } // check the types - var resultType = invoke.GetResultType(flags); + var resultType = invoke.GetResultType(flags); if (resultType is not null && IdentifyDbType(resultType, out _) is null) // don't warn if handled as an inbuilt { var resultMap = MemberMap.CreateForResults(resultType, location); @@ -250,34 +250,9 @@ private void ValidateDapperMethod(in OperationAnalysisContext ctx, IOperation sq { _ = AdditionalCommandState.Parse(GetSymbol(parseState, invoke), parameters, onDiagnostic); } - if (parameters is not null) - { - if (flags.HasAny(OperationFlags.DoNotGenerate)) // using vanilla Dapper mode - { - if (parameters.Members.Any(s => s.IsCancellation) || IsCancellationToken(parameters.ElementType)) - { - ctx.ReportDiagnostic(Diagnostic.Create(Diagnostics.CancellationNotSupported, parameters.Location)); - } - } - else - { - bool first = true; - foreach(var member in parameters.Members) - { - if (member.IsCancellation) - { - if (first) - { - first = false; - } - else - { - ctx.ReportDiagnostic(Diagnostic.Create(Diagnostics.CancellationDuplicated, member.GetLocation())); - } - } - } - } - } + + ValidateParameters(parameters, flags, onDiagnostic); + var args = SharedGetParametersToInclude(parameters, ref flags, sql, onDiagnostic, out var parseFlags); ValidateSql(ctx, sqlSource, GetModeFlags(flags), SqlParameters.From(args), location); @@ -850,6 +825,46 @@ enum ParameterMode ? null : new(rowCountHint, rowCountHintMember?.Member.Name, batchSize, cmdProps); } + static void ValidateParameters(MemberMap? parameters, OperationFlags flags, Action onDiagnostic) + { + if (parameters is null) return; + + var isCancellationElement = IsCancellationToken(parameters.ElementType); + var isFirstCancellation = true; + foreach (var member in parameters.Members) + { + ValidateCancellationTokenParameter(member); + ValidateDbStringParameter(member); + } + + void ValidateDbStringParameter(ElementMember member) + { + if (member.IsDbString) + { + onDiagnostic(Diagnostic.Create(Diagnostics.MoveFromDbString, member.GetLocation())); + } + } + + void ValidateCancellationTokenParameter(ElementMember member) + { + if (flags.HasAny(OperationFlags.DoNotGenerate)) // using vanilla Dapper mode + { + if (isCancellationElement) + { + onDiagnostic(Diagnostic.Create(Diagnostics.CancellationNotSupported, parameters.Location)); + } + } + else + { + if (member.IsCancellation) + { + if (isFirstCancellation) isFirstCancellation = false; + else onDiagnostic(Diagnostic.Create(Diagnostics.CancellationDuplicated, member.GetLocation())); + } + } + } + } + static void ValidateMembers(MemberMap memberMap, Action onDiagnostic) { if (memberMap.Members.Length == 0) diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index e95a2229..166d883c 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -488,6 +488,16 @@ public string DbName public bool IsCancellation => (Kind & ElementMemberKind.Cancellation) != 0; public bool HasDbValueAttribute => _dbValue is not null; + public bool IsDbString => CodeType is + { + Name: "DbString", + TypeKind: TypeKind.Class, + ContainingNamespace: + { + Name: "Dapper" + } + }; + public T? TryGetValue(string memberName) where T : struct => TryGetAttributeValue(_dbValue, memberName, out T value) ? value : null; diff --git a/test/Dapper.AOT.Test/Verifiers/DAP048.cs b/test/Dapper.AOT.Test/Verifiers/DAP048.cs new file mode 100644 index 00000000..2611ce3d --- /dev/null +++ b/test/Dapper.AOT.Test/Verifiers/DAP048.cs @@ -0,0 +1,53 @@ +using Dapper.CodeAnalysis; +using System.Threading.Tasks; +using Xunit; +using static Dapper.CodeAnalysis.DapperAnalyzer; + +namespace Dapper.AOT.Test.Verifiers; + +public class DAP048 : Verifier +{ + [Theory] + [InlineData("[DapperAot(true)]")] + [InlineData("[DapperAot(false)]")] + public Task MoveFromDbString(string dapperAotAttribute) => CSVerifyAsync($$""" + using Dapper; + using System; + using System.Linq; + using System.Data.Common; + using System.Threading; + using System.Threading.Tasks; + + {{dapperAotAttribute}} + class WithoutAot + { + public void DapperCode(DbConnection conn) + { + var sql = "SELECT * FROM Cars WHERE Name = @Name;"; + var cars = conn.Query(sql, + new + { + {|#0:Name|} = new DbString + { + Value = "MyCar", + IsFixedLength = false, + Length = 9, + IsAnsi = true + } + }); + } + } + + public class Car + { + public string Name { get; set; } + public int Speed { get; set; } + } + """, + DefaultConfig, + [ + Diagnostic(Diagnostics.MoveFromDbString) + .WithLocation(0) + ] + ); +} \ No newline at end of file From 08144ecdae8ec9d14cc5ab98ae22cc61cc7a9b62 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 18 Feb 2024 23:13:10 +0100 Subject: [PATCH 02/27] add dbstring example to query --- .../Interceptors/Query.input.cs | 12 ++++ .../Interceptors/Query.output.cs | 57 ++++++++++++++++--- .../Interceptors/Query.output.txt | 2 +- test/Dapper.AOT.Test/Verifiers/DAP048.cs | 2 +- 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/test/Dapper.AOT.Test/Interceptors/Query.input.cs b/test/Dapper.AOT.Test/Interceptors/Query.input.cs index 1a1e64f9..a30bb7df 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.input.cs +++ b/test/Dapper.AOT.Test/Interceptors/Query.input.cs @@ -16,6 +16,18 @@ static async Task SomeCode(DbConnection connection, string bar, bool isBuffered) _ = connection.Query("def", buffered: false, commandType: CommandType.StoredProcedure); _ = connection.Query("def @Foo", obj, buffered: true, commandType: CommandType.Text); + // DbString + _ = connection.Query("select * from Foo where Name = @Name", new + { + Name = new DbString + { + Value = "MyCar", + IsFixedLength = false, + Length = 5, + IsAnsi = true + } + }); + _ = await connection.QueryAsync("def"); _ = await connection.QueryAsync("def", obj); #if !NETFRAMEWORK diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.cs b/test/Dapper.AOT.Test/Interceptors/Query.output.cs index 6e8f3a26..6ae5130c 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.cs @@ -78,8 +78,24 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 19, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync5(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 20, 24)] + internal static global::System.Collections.Generic.IEnumerable Query5(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, HasParameters, Buffered, Text, BindResultsByName, KnownParameters + // takes parameter: + // parameter map: Name + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is not null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory2.Instance).QueryBuffered(param, RowFactory0.Instance); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 31, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync6(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName // returns data: global::Foo.Customer @@ -92,8 +108,8 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 20, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync6(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 32, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync7(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, HasParameters, Buffered, StoredProcedure, BindResultsByName, KnownParameters // takes parameter: @@ -108,8 +124,8 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 22, 47)] - internal static global::System.Collections.Generic.IAsyncEnumerable QueryUnbufferedAsync7(this global::System.Data.Common.DbConnection cnn, string sql, object? param, global::System.Data.Common.DbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 34, 47)] + internal static global::System.Collections.Generic.IAsyncEnumerable QueryUnbufferedAsync8(this global::System.Data.Common.DbConnection cnn, string sql, object? param, global::System.Data.Common.DbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, Unbuffered, StoredProcedure, BindResultsByName // returns data: global::Foo.Customer @@ -121,8 +137,8 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 23, 47)] - internal static global::System.Collections.Generic.IAsyncEnumerable QueryUnbufferedAsync8(this global::System.Data.Common.DbConnection cnn, string sql, object? param, global::System.Data.Common.DbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 35, 47)] + internal static global::System.Collections.Generic.IAsyncEnumerable QueryUnbufferedAsync9(this global::System.Data.Common.DbConnection cnn, string sql, object? param, global::System.Data.Common.DbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, HasParameters, Unbuffered, Text, BindResultsByName, KnownParameters // takes parameter: @@ -283,6 +299,31 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje } + private sealed class CommandFactory2 : CommonCommandFactory // + { + internal static readonly CommandFactory2 Instance = new(); + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)! }); // expected shape + var ps = cmd.Parameters; + global::System.Data.Common.DbParameter p; + p = cmd.CreateParameter(); + p.ParameterName = "Name"; + p.Direction = global::System.Data.ParameterDirection.Input; + p.Value = AsValue(typed.Name); + ps.Add(p); + + } + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)! }); // expected shape + var ps = cmd.Parameters; + ps[0].Value = AsValue(typed.Name); + + } + + } + } } diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.txt b/test/Dapper.AOT.Test/Interceptors/Query.output.txt index 55f86d5c..7e65e705 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.txt +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.txt @@ -1,4 +1,4 @@ Generator produced 1 diagnostics: Hidden DAP000 L1 C1 -Dapper.AOT handled 9 of 9 possible call-sites using 9 interceptors, 2 commands and 1 readers +Dapper.AOT handled 10 of 10 possible call-sites using 10 interceptors, 3 commands and 1 readers diff --git a/test/Dapper.AOT.Test/Verifiers/DAP048.cs b/test/Dapper.AOT.Test/Verifiers/DAP048.cs index 2611ce3d..dae396a5 100644 --- a/test/Dapper.AOT.Test/Verifiers/DAP048.cs +++ b/test/Dapper.AOT.Test/Verifiers/DAP048.cs @@ -31,7 +31,7 @@ public void DapperCode(DbConnection conn) { Value = "MyCar", IsFixedLength = false, - Length = 9, + Length = 5, IsAnsi = true } }); From b5fc745b13085253652bb43e8eec0ab8bd3cdb18 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 28 Apr 2024 17:54:31 +0200 Subject: [PATCH 03/27] create test --- docs/rules/DAP048.md | 38 ++++++--- .../Interceptors/DbString.input.cs | 30 +++++++ .../Interceptors/DbString.output.cs | 85 +++++++++++++++++++ .../Interceptors/DbString.output.txt | 4 + test/Dapper.AOT.Test/Verifiers/DAP048.cs | 1 - 5 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 test/Dapper.AOT.Test/Interceptors/DbString.input.cs create mode 100644 test/Dapper.AOT.Test/Interceptors/DbString.output.cs create mode 100644 test/Dapper.AOT.Test/Interceptors/DbString.output.txt diff --git a/docs/rules/DAP048.md b/docs/rules/DAP048.md index 6e66ec20..8c59cc06 100644 --- a/docs/rules/DAP048.md +++ b/docs/rules/DAP048.md @@ -1,26 +1,44 @@ # DAP048 -[DbString](https://github.com/DapperLib/Dapper/blob/main/Dapper/DbString.cs) -achieves the same as [DbValueAttribute](https://github.com/DapperLib/DapperAOT/blob/main/src/Dapper.AOT/DbValueAttribute.cs) -on which a lot of features rely. +[DbString](https://github.com/DapperLib/Dapper/blob/main/Dapper/DbString.cs) is allocatey type, but achieves the same as +[DbValueAttribute](https://github.com/DapperLib/DapperAOT/blob/main/src/Dapper.AOT/DbValueAttribute.cs). -// TODO Bad: ``` c# -public class MyType +public void DapperCode(DbConnection conn) { - public string _firstName; - public string first_name; + var sql = "SELECT COUNT(*) FROM Foo WHERE Name = @Name;"; + var cars = conn.Query(sql, + new + { + Name = new DbString + { + Value = "MyFoo", + IsFixedLength = false, + Length = 5, + IsAnsi = true + } + }); } ``` Good: ``` c# -public class MyType +public void DapperCode(DbConnection conn) { - public string firstName; - public string firstNaming; + var sql = "SELECT COUNT(*) FROM Foo WHERE Name = @Name;"; + var cars = conn.Query(sql, + new MyPoco + { + Name = "MyFoo" + }); +} + +class MyPoco +{ + [DbValue(Length = 5, DbType = DbType.AnsiStringFixedLength)] // specify properties here + public string Name { get; set; } } ``` diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.input.cs b/test/Dapper.AOT.Test/Interceptors/DbString.input.cs new file mode 100644 index 00000000..621a271d --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/DbString.input.cs @@ -0,0 +1,30 @@ +using Dapper; +using System.Data.Common; +using System.Threading.Tasks; + +[module: DapperAot] + +public static class Foo +{ + static async Task SomeCode(DbConnection connection) + { + _ = await connection.QueryAsync("select * from Orders where name = @name", new + { + name = new DbString + { + Value = "myOrder", + IsFixedLength = false, + Length = 7, + IsAnsi = true + } + }); + + } + + public class Customer + { + public int X { get; set; } + public string Y; + public double? Z { get; set; } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs new file mode 100644 index 00000000..77eb5992 --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs @@ -0,0 +1,85 @@ +#nullable enable +namespace Dapper.AOT // interceptors must be in a known namespace +{ + file static class DapperGeneratedInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DbString.input.cs", 11, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, HasParameters, Buffered, Text, KnownParameters + // takes parameter: + // parameter map: name + // returns data: int + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(param is not null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory0.Instance).QueryBufferedAsync(param, global::Dapper.RowFactory.Inbuilt.Value())); + + } + + private class CommonCommandFactory : global::Dapper.CommandFactory + { + public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) + { + var cmd = base.GetCommand(connection, sql, commandType, args); + // apply special per-provider command initialization logic for OracleCommand + if (cmd is global::Oracle.ManagedDataAccess.Client.OracleCommand cmd0) + { + cmd0.BindByName = true; + cmd0.InitialLONGFetchSize = -1; + + } + return cmd; + } + + } + + private static readonly CommonCommandFactory DefaultCommandFactory = new(); + + private sealed class CommandFactory0 : CommonCommandFactory // + { + internal static readonly CommandFactory0 Instance = new(); + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { name = default(global::Dapper.DbString)! }); // expected shape + var ps = cmd.Parameters; + global::System.Data.Common.DbParameter p; + p = cmd.CreateParameter(); + p.ParameterName = "name"; + p.Direction = global::System.Data.ParameterDirection.Input; + p.Value = AsValue(typed.name); + ps.Add(p); + + } + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { name = default(global::Dapper.DbString)! }); // expected shape + var ps = cmd.Parameters; + ps[0].Value = AsValue(typed.name); + + } + + } + + + } +} +namespace System.Runtime.CompilerServices +{ + // this type is needed by the compiler to implement interceptors - it doesn't need to + // come from the runtime itself, though + + [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + sealed file class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) + { + _ = path; + _ = lineNumber; + _ = columnNumber; + } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.txt b/test/Dapper.AOT.Test/Interceptors/DbString.output.txt new file mode 100644 index 00000000..6aee302d --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.txt @@ -0,0 +1,4 @@ +Generator produced 1 diagnostics: + +Hidden DAP000 L1 C1 +Dapper.AOT handled 1 of 1 possible call-sites using 1 interceptors, 1 commands and 0 readers diff --git a/test/Dapper.AOT.Test/Verifiers/DAP048.cs b/test/Dapper.AOT.Test/Verifiers/DAP048.cs index dae396a5..60ae402a 100644 --- a/test/Dapper.AOT.Test/Verifiers/DAP048.cs +++ b/test/Dapper.AOT.Test/Verifiers/DAP048.cs @@ -9,7 +9,6 @@ public class DAP048 : Verifier { [Theory] [InlineData("[DapperAot(true)]")] - [InlineData("[DapperAot(false)]")] public Task MoveFromDbString(string dapperAotAttribute) => CSVerifyAsync($$""" using Dapper; using System; From 20bba94ec49b5b4cff363fe9a7d1cbd0a1f0feb8 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 28 Apr 2024 23:13:18 +0200 Subject: [PATCH 04/27] start processing --- .../CodeAnalysis/DapperAnalyzer.cs | 2 +- .../DapperInterceptorGenerator.cs | 21 +++++++++- .../Internal/Inspection.cs | 36 +++++++++++++---- .../Interceptors/DbString.input.cs | 16 ++++++-- .../Interceptors/DbString.output.cs | 39 +++++++++++++++++++ .../Interceptors/DbString.output.txt | 2 +- 6 files changed, 100 insertions(+), 16 deletions(-) diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs index ec58be92..825b1417 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs @@ -839,7 +839,7 @@ static void ValidateParameters(MemberMap? parameters, OperationFlags flags, Acti void ValidateDbStringParameter(ElementMember member) { - if (member.IsDbString) + if (member.DapperSpecialType == DapperSpecialType.DbString) { onDiagnostic(Diagnostic.Create(Diagnostics.MoveFromDbString, member.GetLocation())); } diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs index 83fd1be8..cf9c4beb 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Text; using System.Threading; +using static Dapper.Internal.Inspection; namespace Dapper.CodeAnalysis; @@ -1076,8 +1077,19 @@ private static void WriteArgs(ITypeSymbol? parameterType, CodeWriter sb, WriteAr sb.Append("p = cmd.CreateParameter();").NewLine() .Append("p.ParameterName = ").AppendVerbatimLiteral(member.DbName).Append(";").NewLine(); - var dbType = member.GetDbType(out _); - var size = member.TryGetValue("Size"); + // how to calculate DbType / Size / etc if I am writing shared code for type here? + // I need specifics for each usage case + + var dbType = member.DapperSpecialType switch + { + DapperSpecialType.DbString => member.GetDbType(out _), + _ => member.GetDbType(out _) + }; + var size = member.DapperSpecialType switch + { + _ => member.TryGetValue("Size") + }; + bool useSetValueWithDefaultSize = false; if (dbType is not null) { @@ -1183,6 +1195,11 @@ private static void WriteArgs(ITypeSymbol? parameterType, CodeWriter sb, WriteAr } } + static void CalculateDbSyze() + { + + } + static void AppendDbParameterSetting(CodeWriter sb, string memberName, int? value) { if (value is not null) diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index 166d883c..00c4d359 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -421,6 +421,13 @@ public enum ElementMemberKind Cancellation = 1 << 2, } + [Flags] + internal enum DapperSpecialType + { + None = 0, + DbString = 1 << 0, + } + internal static bool IsCancellationToken(ITypeSymbol? type) => type is INamedTypeSymbol { @@ -488,15 +495,23 @@ public string DbName public bool IsCancellation => (Kind & ElementMemberKind.Cancellation) != 0; public bool HasDbValueAttribute => _dbValue is not null; - public bool IsDbString => CodeType is - { - Name: "DbString", - TypeKind: TypeKind.Class, - ContainingNamespace: + public DapperSpecialType DapperSpecialType + { + get { - Name: "Dapper" - } - }; + if (CodeType is { + Name: "DbString", + TypeKind: TypeKind.Class, + ContainingNamespace: + { + Name: "Dapper", + IsGlobalNamespace: true + } + }) return DapperSpecialType.DbString; + + return DapperSpecialType.None; + } + } public T? TryGetValue(string memberName) where T : struct => TryGetAttributeValue(_dbValue, memberName, out T value) ? value : null; @@ -1107,6 +1122,11 @@ public static ITypeSymbol MakeNonNullable(ITypeSymbol type) readerMethod = null; return DbType.DateTimeOffset; } + if (type.Name == "DbString" && type.ContainingNamespace is { Name: "Dapper", ContainingNamespace.IsGlobalNamespace: true }) + { + readerMethod = null; + return DbType.String; + } readerMethod = null; return null; } diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.input.cs b/test/Dapper.AOT.Test/Interceptors/DbString.input.cs index 621a271d..541d8cb1 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.input.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.input.cs @@ -19,12 +19,20 @@ static async Task SomeCode(DbConnection connection) } }); + //_ = await connection.QueryAsync("select * from Orders where name = @name", new Poco + //{ + // Name = new DbString + // { + // Value = "myOrder", + // IsFixedLength = false, + // Length = 7, + // IsAnsi = true + // } + //}); } - public class Customer + public class Poco { - public int X { get; set; } - public string Y; - public double? Z { get; set; } + public DbString Name { get; set; } } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs index 77eb5992..0afb3dd2 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs @@ -19,6 +19,22 @@ file static class DapperGeneratedInterceptors } + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DbString.input.cs", 22, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, HasParameters, Buffered, Text, KnownParameters + // takes parameter: global::Foo.Poco + // parameter map: Name + // returns data: int + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(param is not null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory1.Instance).QueryBufferedAsync((global::Foo.Poco)param!, global::Dapper.RowFactory.Inbuilt.Value())); + + } + private class CommonCommandFactory : global::Dapper.CommandFactory { public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) @@ -63,6 +79,29 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje } + private sealed class CommandFactory1 : CommonCommandFactory + { + internal static readonly CommandFactory1 Instance = new(); + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.Poco args) + { + var ps = cmd.Parameters; + global::System.Data.Common.DbParameter p; + p = cmd.CreateParameter(); + p.ParameterName = "Name"; + p.Direction = global::System.Data.ParameterDirection.Input; + p.Value = AsValue(args.Name); + ps.Add(p); + + } + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.Poco args) + { + var ps = cmd.Parameters; + ps[0].Value = AsValue(args.Name); + + } + + } + } } diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.txt b/test/Dapper.AOT.Test/Interceptors/DbString.output.txt index 6aee302d..29906b6b 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.txt +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.txt @@ -1,4 +1,4 @@ Generator produced 1 diagnostics: Hidden DAP000 L1 C1 -Dapper.AOT handled 1 of 1 possible call-sites using 1 interceptors, 1 commands and 0 readers +Dapper.AOT handled 2 of 2 possible call-sites using 2 interceptors, 2 commands and 0 readers From 43aa8eced111c6606a074a255bbd94b91596e0aa Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Wed, 1 May 2024 18:44:33 +0200 Subject: [PATCH 05/27] support DbString! --- .../DapperInterceptorGenerator.cs | 46 +++++------ .../CodeAnalysis/GeneratorContext.cs | 28 +++++++ .../CodeAnalysis/ParseState.cs | 1 + .../TypeAccessorInterceptorGenerator.cs | 4 +- ...uteWriter.cs => PreGeneratedCodeWriter.cs} | 33 +++++--- .../Dapper.AOT.Analyzers.csproj | 6 +- .../InGeneration/DapperHelpers.cs | 39 ++++++++++ .../InterceptsLocationAttribute.cs | 0 .../IncludedGeneration.cs | 12 +++ .../Internal/Inspection.cs | 3 +- test/Dapper.AOT.Test/Dapper.AOT.Test.csproj | 4 +- .../InGeneration/DbStringHelpersTests.cs | 35 +++++++++ .../Interceptors/DbString.input.cs | 31 ++++---- .../Interceptors/DbString.output.cs | 77 ++++++++++++++++--- .../Interceptors/Query.output.cs | 44 ++++++++++- 15 files changed, 295 insertions(+), 68 deletions(-) create mode 100644 src/Dapper.AOT.Analyzers/CodeAnalysis/GeneratorContext.cs rename src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/{InterceptorsLocationAttributeWriter.cs => PreGeneratedCodeWriter.cs} (54%) create mode 100644 src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs rename src/Dapper.AOT.Analyzers/{ => InGeneration}/InterceptsLocationAttribute.cs (100%) create mode 100644 src/Dapper.AOT.Analyzers/IncludedGeneration.cs create mode 100644 test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs index cf9c4beb..f314d49b 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs @@ -16,6 +16,7 @@ using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Threading; using static Dapper.Internal.Inspection; @@ -424,16 +425,15 @@ internal void Generate(in GenerateState ctx) WriteRowFactory(ctx, sb, pair.Type, pair.Index); } - foreach (var tuple in factories) { WriteCommandFactory(ctx, baseCommandFactory, sb, tuple.Type, tuple.Index, tuple.Map, tuple.CacheCount, tuple.AdditionalCommandState); } sb.Outdent().Outdent(); // ends our generated file-scoped class and the namespace - - var interceptsLocationWriter = new InterceptorsLocationAttributeWriter(sb); - interceptsLocationWriter.Write(ctx.Compilation); + + var preGeneratedCodeWriter = new PreGeneratedCodeWriter(sb, ctx.Compilation); + preGeneratedCodeWriter.Write(ctx.GeneratorContext.IncludedGenerationTypes); ctx.AddSource((ctx.Compilation.AssemblyName ?? "package") + ".generated.cs", sb.ToString()); ctx.ReportDiagnostic(Diagnostic.Create(Diagnostics.InterceptorsGenerated, null, callSiteCount, ctx.Nodes.Length, methodIndex, factories.Count(), readers.Count())); @@ -491,11 +491,11 @@ private static void WriteCommandFactory(in GenerateState ctx, string baseFactory else { sb.Append("public override void AddParameters(in global::Dapper.UnifiedCommand cmd, ").Append(declaredType).Append(" args)").Indent().NewLine(); - WriteArgs(type, sb, WriteArgsMode.Add, map, ref flags); + WriteArgs(in ctx, type, sb, WriteArgsMode.Add, map, ref flags); sb.Outdent().NewLine(); sb.Append("public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, ").Append(declaredType).Append(" args)").Indent().NewLine(); - WriteArgs(type, sb, WriteArgsMode.Update, map, ref flags); + WriteArgs(in ctx, type, sb, WriteArgsMode.Update, map, ref flags); sb.Outdent().NewLine(); if ((flags & (WriteArgsFlags.NeedsRowCount | WriteArgsFlags.NeedsPostProcess)) != 0) @@ -508,11 +508,11 @@ private static void WriteCommandFactory(in GenerateState ctx, string baseFactory sb.Append("public override void PostProcess(in global::Dapper.UnifiedCommand cmd, ").Append(declaredType).Append(" args, int rowCount)").Indent().NewLine(); if ((flags & WriteArgsFlags.NeedsPostProcess) != 0) { - WriteArgs(type, sb, WriteArgsMode.PostProcess, map, ref flags); + WriteArgs(in ctx, type, sb, WriteArgsMode.PostProcess, map, ref flags); } if ((flags & WriteArgsFlags.NeedsRowCount) != 0) { - WriteArgs(type, sb, WriteArgsMode.SetRowCount, map, ref flags); + WriteArgs(in ctx, type, sb, WriteArgsMode.SetRowCount, map, ref flags); } if (baseFactory != DapperBaseCommandFactory) { @@ -525,7 +525,7 @@ private static void WriteCommandFactory(in GenerateState ctx, string baseFactory { sb.Append("public override global::System.Threading.CancellationToken GetCancellationToken(").Append(declaredType).Append(" args)") .Indent().NewLine(); - WriteArgs(type, sb, WriteArgsMode.GetCancellationToken, map, ref flags); + WriteArgs(in ctx, type, sb, WriteArgsMode.GetCancellationToken, map, ref flags); sb.Outdent().NewLine(); } } @@ -967,7 +967,7 @@ enum WriteArgsMode GetCancellationToken } - private static void WriteArgs(ITypeSymbol? parameterType, CodeWriter sb, WriteArgsMode mode, string map, ref WriteArgsFlags flags) + private static void WriteArgs(in GenerateState ctx, ITypeSymbol? parameterType, CodeWriter sb, WriteArgsMode mode, string map, ref WriteArgsFlags flags) { if (parameterType is null) { @@ -1074,22 +1074,22 @@ private static void WriteArgs(ITypeSymbol? parameterType, CodeWriter sb, WriteAr switch (mode) { case WriteArgsMode.Add: - sb.Append("p = cmd.CreateParameter();").NewLine() - .Append("p.ParameterName = ").AppendVerbatimLiteral(member.DbName).Append(";").NewLine(); + sb.Append("p = cmd.CreateParameter();").NewLine(); + + if (member.DapperSpecialType is DapperSpecialType.DbString) + { + ctx.GeneratorContext.IncludeGenerationType(IncludedGeneration.DbStringHelpers); - // how to calculate DbType / Size / etc if I am writing shared code for type here? - // I need specifics for each usage case + sb.Append("p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, ") + .Append(source).Append(".").Append(member.DbName).Append(");").NewLine(); - var dbType = member.DapperSpecialType switch - { - DapperSpecialType.DbString => member.GetDbType(out _), - _ => member.GetDbType(out _) - }; - var size = member.DapperSpecialType switch - { - _ => member.TryGetValue("Size") - }; + sb.Append("ps.Add(p);").NewLine(); // dont forget to add parameter to command parameters collection + break; + } + sb.Append("p.ParameterName = ").AppendVerbatimLiteral(member.DbName).Append(";").NewLine(); + var dbType = member.GetDbType(out _); + var size = member.TryGetValue("Size"); bool useSetValueWithDefaultSize = false; if (dbType is not null) { diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/GeneratorContext.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/GeneratorContext.cs new file mode 100644 index 00000000..db8aa9fd --- /dev/null +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/GeneratorContext.cs @@ -0,0 +1,28 @@ +namespace Dapper.CodeAnalysis +{ + /// + /// Contains data about current generation run. + /// + internal class GeneratorContext + { + /// + /// Specifies which generation types should be included in the output. + /// + public IncludedGeneration IncludedGenerationTypes { get; private set; } + + public GeneratorContext() + { + // set default included generation types here + IncludedGenerationTypes = IncludedGeneration.InterceptsLocationAttribute; + } + + /// + /// Adds another generation type to the list of already included types. + /// + /// another generation type to include in the output + public void IncludeGenerationType(IncludedGeneration anotherType) + { + IncludedGenerationTypes |= anotherType; + } + } +} diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/ParseState.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/ParseState.cs index a1b2ea09..ee738995 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/ParseState.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/ParseState.cs @@ -82,6 +82,7 @@ public GenerateState(SourceProductionContext ctx, in (Compilation Compilation, I private readonly GenerateContextProxy? proxy; public readonly ImmutableArray Nodes; public readonly Compilation Compilation; + public readonly GeneratorContext GeneratorContext = new(); internal void ReportDiagnostic(Diagnostic diagnostic) { diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/TypeAccessorInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/TypeAccessorInterceptorGenerator.cs index 966ccc60..3c806806 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/TypeAccessorInterceptorGenerator.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/TypeAccessorInterceptorGenerator.cs @@ -164,8 +164,8 @@ void ReportDiagnosticInUsages(DiagnosticDescriptor diagnosticDescriptor) } }); - var interceptsLocWriter = new InterceptorsLocationAttributeWriter(codeWriter); - interceptsLocWriter.Write(state.Compilation); + var preGenerator = new PreGeneratedCodeWriter(codeWriter, state.Compilation); + preGenerator.Write(IncludedGeneration.InterceptsLocationAttribute); context.AddSource((state.Compilation.AssemblyName ?? "package") + ".generated.cs", sb.GetSourceText()); } diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/InterceptorsLocationAttributeWriter.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/PreGeneratedCodeWriter.cs similarity index 54% rename from src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/InterceptorsLocationAttributeWriter.cs rename to src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/PreGeneratedCodeWriter.cs index 360e415b..32e92e07 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/InterceptorsLocationAttributeWriter.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/PreGeneratedCodeWriter.cs @@ -3,25 +3,38 @@ namespace Dapper.CodeAnalysis.Writers { - internal struct InterceptorsLocationAttributeWriter + internal struct PreGeneratedCodeWriter { + readonly Compilation _compilation; readonly CodeWriter _codeWriter; - public InterceptorsLocationAttributeWriter(CodeWriter codeWriter) + public PreGeneratedCodeWriter( + CodeWriter codeWriter, + Compilation compilation) { _codeWriter = codeWriter; + _compilation = compilation; } - /// - /// Writes the "InterceptsLocationAttribute" to inner . - /// - /// Does so only when "InterceptsLocationAttribute" is NOT visible by . - public void Write(Compilation compilation) + public void Write(IncludedGeneration includedGenerations) { - var attrib = compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.InterceptsLocationAttribute"); - if (!IsAvailable(attrib, compilation)) + if (includedGenerations.HasFlag(IncludedGeneration.InterceptsLocationAttribute)) { - _codeWriter.NewLine().Append(Resources.ReadString("Dapper.InterceptsLocationAttribute.cs")); + WriteInterceptsLocationAttribute(); + } + + if (includedGenerations.HasFlag(IncludedGeneration.DbStringHelpers)) + { + _codeWriter.NewLine().Append(Resources.ReadString("Dapper.InGeneration.DapperHelpers.cs")); + } + } + + void WriteInterceptsLocationAttribute() + { + var attrib = _compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.InterceptsLocationAttribute"); + if (!IsAvailable(attrib, _compilation)) + { + _codeWriter.NewLine().Append(Resources.ReadString("Dapper.InGeneration.InterceptsLocationAttribute.cs")); } static bool IsAvailable(INamedTypeSymbol? type, Compilation compilation) diff --git a/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj b/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj index 2145e967..f74b74e8 100644 --- a/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj +++ b/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj @@ -16,9 +16,11 @@ - + + - + + DapperInterceptorGenerator.cs diff --git a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs new file mode 100644 index 00000000..f24d20f2 --- /dev/null +++ b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs @@ -0,0 +1,39 @@ +namespace Dapper.Aot.Generated +{ + /// + /// Contains helpers to properly handle + /// + static class DbStringHelpers + { + public static global::System.Data.Common.DbParameter ConvertToDbParameter( + global::System.Data.Common.DbParameter dbParameter, + global::Dapper.DbString? dbString) + { + if (dbString is null) + { + dbParameter.Value = global::System.DBNull.Value; + return dbParameter; + } + + dbParameter.Size = dbString switch + { + { Value: null } => 0, + { IsAnsi: false } => global::System.Text.Encoding.ASCII.GetByteCount(dbString.Value), + { IsAnsi: true } => global::System.Text.Encoding.Default.GetByteCount(dbString.Value), + _ => default + }; + dbParameter.DbType = dbString switch + { + { IsAnsi: true, IsFixedLength: true } => global::System.Data.DbType.AnsiStringFixedLength, + { IsAnsi: true, IsFixedLength: false } => global::System.Data.DbType.AnsiString, + { IsAnsi: false, IsFixedLength: true } => global::System.Data.DbType.StringFixedLength, + { IsAnsi: false, IsFixedLength: false } => global::System.Data.DbType.String, + _ => dbParameter.DbType + }; + + dbParameter.Value = dbString.Value as object ?? global::System.DBNull.Value; + + return dbParameter; + } + } +} \ No newline at end of file diff --git a/src/Dapper.AOT.Analyzers/InterceptsLocationAttribute.cs b/src/Dapper.AOT.Analyzers/InGeneration/InterceptsLocationAttribute.cs similarity index 100% rename from src/Dapper.AOT.Analyzers/InterceptsLocationAttribute.cs rename to src/Dapper.AOT.Analyzers/InGeneration/InterceptsLocationAttribute.cs diff --git a/src/Dapper.AOT.Analyzers/IncludedGeneration.cs b/src/Dapper.AOT.Analyzers/IncludedGeneration.cs new file mode 100644 index 00000000..dc540e71 --- /dev/null +++ b/src/Dapper.AOT.Analyzers/IncludedGeneration.cs @@ -0,0 +1,12 @@ +using System; + +namespace Dapper +{ + [Flags] + internal enum IncludedGeneration + { + None = 0, + InterceptsLocationAttribute = 1 << 0, + DbStringHelpers = 1 << 1, + } +} diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index 00c4d359..8c13195a 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -504,8 +504,7 @@ public DapperSpecialType DapperSpecialType TypeKind: TypeKind.Class, ContainingNamespace: { - Name: "Dapper", - IsGlobalNamespace: true + Name: "Dapper" } }) return DapperSpecialType.DbString; diff --git a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj index 10da715a..6245b025 100644 --- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj +++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj @@ -12,9 +12,11 @@ - + + + $([System.String]::Copy(%(Filename)).Replace('.output', '.input.cs')) diff --git a/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs b/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs new file mode 100644 index 00000000..29d3b0b8 --- /dev/null +++ b/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs @@ -0,0 +1,35 @@ +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; +using Xunit; + +namespace Dapper.AOT.Test.InGeneration +{ + + public class DbStringHelpersTests + { + [Theory] + [InlineData(false, false, "qweqwe", DbType.String, 6)] + [InlineData(false, true, "qweqwe", DbType.StringFixedLength, 6)] + [InlineData(true, false, "qweqwe", DbType.AnsiString, 6)] + [InlineData(true, true, "qweqwe", DbType.AnsiStringFixedLength, 6)] + public void ConfigureDbString_ShouldProperlySetupDbParameter(bool isAnsi, bool isFixedLength, string dbStringValue, DbType expectedDbType, int expectedSize) + { + var param = CreateDbParameter(); + var dbString = new DbString + { + IsAnsi = isAnsi, + IsFixedLength = isFixedLength, + Value = dbStringValue, + Length = 60 + }; + + param = Aot.Generated.DbStringHelpers.ConvertToDbParameter(param, dbString); + + Assert.Equal(expectedSize, param.Size); + Assert.Equal(expectedDbType, param.DbType); + } + + DbParameter CreateDbParameter() => new SqlParameter(); + } +} diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.input.cs b/test/Dapper.AOT.Test/Interceptors/DbString.input.cs index 541d8cb1..633ce3e0 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.input.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.input.cs @@ -8,31 +8,36 @@ public static class Foo { static async Task SomeCode(DbConnection connection) { - _ = await connection.QueryAsync("select * from Orders where name = @name", new + _ = await connection.QueryAsync("select * from Orders where name = @Name and id = @Id", new { - name = new DbString + Name = new DbString { Value = "myOrder", IsFixedLength = false, Length = 7, IsAnsi = true - } + }, + + Id = 123 }); - //_ = await connection.QueryAsync("select * from Orders where name = @name", new Poco - //{ - // Name = new DbString - // { - // Value = "myOrder", - // IsFixedLength = false, - // Length = 7, - // IsAnsi = true - // } - //}); + _ = await connection.QueryAsync("select * from Orders where name = @Name and id = @Id", new Poco + { + Name = new DbString + { + Value = "myOrder", + IsFixedLength = false, + Length = 7, + IsAnsi = true + }, + + Id = 123 + }); } public class Poco { public DbString Name { get; set; } + public int Id { get; set; } } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs index 0afb3dd2..e6c5231c 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs @@ -7,8 +7,8 @@ file static class DapperGeneratedInterceptors internal static global::System.Threading.Tasks.Task> QueryAsync0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, HasParameters, Buffered, Text, KnownParameters - // takes parameter: - // parameter map: name + // takes parameter: + // parameter map: Id Name // returns data: int global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); @@ -19,12 +19,12 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DbString.input.cs", 22, 30)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DbString.input.cs", 24, 30)] internal static global::System.Threading.Tasks.Task> QueryAsync1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, HasParameters, Buffered, Text, KnownParameters // takes parameter: global::Foo.Poco - // parameter map: Name + // parameter map: Id Name // returns data: int global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); @@ -54,28 +54,35 @@ private class CommonCommandFactory : global::Dapper.CommandFactory private static readonly CommonCommandFactory DefaultCommandFactory = new(); - private sealed class CommandFactory0 : CommonCommandFactory // + private sealed class CommandFactory0 : CommonCommandFactory // { internal static readonly CommandFactory0 Instance = new(); public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) { - var typed = Cast(args, static () => new { name = default(global::Dapper.DbString)! }); // expected shape + var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)!, Id = default(int) }); // expected shape var ps = cmd.Parameters; global::System.Data.Common.DbParameter p; p = cmd.CreateParameter(); - p.ParameterName = "name"; + p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, typed.Name); + ps.Add(p); + + p = cmd.CreateParameter(); + p.ParameterName = "Id"; + p.DbType = global::System.Data.DbType.Int32; p.Direction = global::System.Data.ParameterDirection.Input; - p.Value = AsValue(typed.name); + p.Value = AsValue(typed.Id); ps.Add(p); } public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) { - var typed = Cast(args, static () => new { name = default(global::Dapper.DbString)! }); // expected shape + var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)!, Id = default(int) }); // expected shape var ps = cmd.Parameters; - ps[0].Value = AsValue(typed.name); + ps[0].Value = AsValue(typed.Name); + ps[1].Value = AsValue(typed.Id); } + public override bool CanPrepare => true; } @@ -87,9 +94,14 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global: var ps = cmd.Parameters; global::System.Data.Common.DbParameter p; p = cmd.CreateParameter(); - p.ParameterName = "Name"; + p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, args.Name); + ps.Add(p); + + p = cmd.CreateParameter(); + p.ParameterName = "Id"; + p.DbType = global::System.Data.DbType.Int32; p.Direction = global::System.Data.ParameterDirection.Input; - p.Value = AsValue(args.Name); + p.Value = AsValue(args.Id); ps.Add(p); } @@ -97,8 +109,10 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, glob { var ps = cmd.Parameters; ps[0].Value = AsValue(args.Name); + ps[1].Value = AsValue(args.Id); } + public override bool CanPrepare => true; } @@ -121,4 +135,43 @@ public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber _ = columnNumber; } } +} +namespace Dapper.Aot.Generated +{ + /// + /// Contains helpers to properly handle + /// + static class DbStringHelpers + { + public static global::System.Data.Common.DbParameter ConvertToDbParameter( + global::System.Data.Common.DbParameter dbParameter, + global::Dapper.DbString? dbString) + { + if (dbString is null) + { + dbParameter.Value = global::System.DBNull.Value; + return dbParameter; + } + + dbParameter.Size = dbString switch + { + { Value: null } => 0, + { IsAnsi: false } => global::System.Text.Encoding.ASCII.GetByteCount(dbString.Value), + { IsAnsi: true } => global::System.Text.Encoding.Default.GetByteCount(dbString.Value), + _ => default + }; + dbParameter.DbType = dbString switch + { + { IsAnsi: true, IsFixedLength: true } => global::System.Data.DbType.AnsiStringFixedLength, + { IsAnsi: true, IsFixedLength: false } => global::System.Data.DbType.AnsiString, + { IsAnsi: false, IsFixedLength: true } => global::System.Data.DbType.StringFixedLength, + { IsAnsi: false, IsFixedLength: false } => global::System.Data.DbType.String, + _ => dbParameter.DbType + }; + + dbParameter.Value = dbString.Value as object ?? global::System.DBNull.Value; + + return dbParameter; + } + } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.cs b/test/Dapper.AOT.Test/Interceptors/Query.output.cs index 6ae5130c..a7fb54be 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.cs @@ -308,9 +308,7 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? var ps = cmd.Parameters; global::System.Data.Common.DbParameter p; p = cmd.CreateParameter(); - p.ParameterName = "Name"; - p.Direction = global::System.Data.ParameterDirection.Input; - p.Value = AsValue(typed.Name); + p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, typed.Name); ps.Add(p); } @@ -321,6 +319,7 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje ps[0].Value = AsValue(typed.Name); } + public override bool CanPrepare => true; } @@ -343,4 +342,43 @@ public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber _ = columnNumber; } } +} +namespace Dapper.Aot.Generated +{ + /// + /// Contains helpers to properly handle + /// + static class DbStringHelpers + { + public static global::System.Data.Common.DbParameter ConvertToDbParameter( + global::System.Data.Common.DbParameter dbParameter, + global::Dapper.DbString? dbString) + { + if (dbString is null) + { + dbParameter.Value = global::System.DBNull.Value; + return dbParameter; + } + + dbParameter.Size = dbString switch + { + { Value: null } => 0, + { IsAnsi: false } => global::System.Text.Encoding.ASCII.GetByteCount(dbString.Value), + { IsAnsi: true } => global::System.Text.Encoding.Default.GetByteCount(dbString.Value), + _ => default + }; + dbParameter.DbType = dbString switch + { + { IsAnsi: true, IsFixedLength: true } => global::System.Data.DbType.AnsiStringFixedLength, + { IsAnsi: true, IsFixedLength: false } => global::System.Data.DbType.AnsiString, + { IsAnsi: false, IsFixedLength: true } => global::System.Data.DbType.StringFixedLength, + { IsAnsi: false, IsFixedLength: false } => global::System.Data.DbType.String, + _ => dbParameter.DbType + }; + + dbParameter.Value = dbString.Value as object ?? global::System.DBNull.Value; + + return dbParameter; + } + } } \ No newline at end of file From 9965bd671d23140975dd4a411914038f76ae3f3a Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Wed, 1 May 2024 18:46:35 +0200 Subject: [PATCH 06/27] last checks --- docs/rules/DAP048.md | 2 +- test/Dapper.AOT.Test/Verifiers/DAP048.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/rules/DAP048.md b/docs/rules/DAP048.md index 8c59cc06..3dfec4ad 100644 --- a/docs/rules/DAP048.md +++ b/docs/rules/DAP048.md @@ -1,6 +1,6 @@ # DAP048 -[DbString](https://github.com/DapperLib/Dapper/blob/main/Dapper/DbString.cs) is allocatey type, but achieves the same as +[DbString](https://github.com/DapperLib/Dapper/blob/main/Dapper/DbString.cs) is allocatey type, but achieves the same as [DbValueAttribute](https://github.com/DapperLib/DapperAOT/blob/main/src/Dapper.AOT/DbValueAttribute.cs). Bad: diff --git a/test/Dapper.AOT.Test/Verifiers/DAP048.cs b/test/Dapper.AOT.Test/Verifiers/DAP048.cs index 60ae402a..8da724bf 100644 --- a/test/Dapper.AOT.Test/Verifiers/DAP048.cs +++ b/test/Dapper.AOT.Test/Verifiers/DAP048.cs @@ -8,6 +8,7 @@ namespace Dapper.AOT.Test.Verifiers; public class DAP048 : Verifier { [Theory] + [InlineData("[DapperAot(false)]")] [InlineData("[DapperAot(true)]")] public Task MoveFromDbString(string dapperAotAttribute) => CSVerifyAsync($$""" using Dapper; From d372298c502af742d8e9571483d8c83fc77790aa Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Wed, 1 May 2024 18:51:11 +0200 Subject: [PATCH 07/27] regenerate for netfx --- .../Interceptors/DbString.output.netfx.cs | 177 ++++++++++++++++++ .../Interceptors/DbString.output.netfx.txt | 4 + .../Interceptors/Query.output.netfx.cs | 87 ++++++++- .../Interceptors/Query.output.netfx.txt | 2 +- 4 files changed, 265 insertions(+), 5 deletions(-) create mode 100644 test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs create mode 100644 test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.txt diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs new file mode 100644 index 00000000..e6c5231c --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs @@ -0,0 +1,177 @@ +#nullable enable +namespace Dapper.AOT // interceptors must be in a known namespace +{ + file static class DapperGeneratedInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DbString.input.cs", 11, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, HasParameters, Buffered, Text, KnownParameters + // takes parameter: + // parameter map: Id Name + // returns data: int + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(param is not null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory0.Instance).QueryBufferedAsync(param, global::Dapper.RowFactory.Inbuilt.Value())); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DbString.input.cs", 24, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, HasParameters, Buffered, Text, KnownParameters + // takes parameter: global::Foo.Poco + // parameter map: Id Name + // returns data: int + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(param is not null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory1.Instance).QueryBufferedAsync((global::Foo.Poco)param!, global::Dapper.RowFactory.Inbuilt.Value())); + + } + + private class CommonCommandFactory : global::Dapper.CommandFactory + { + public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) + { + var cmd = base.GetCommand(connection, sql, commandType, args); + // apply special per-provider command initialization logic for OracleCommand + if (cmd is global::Oracle.ManagedDataAccess.Client.OracleCommand cmd0) + { + cmd0.BindByName = true; + cmd0.InitialLONGFetchSize = -1; + + } + return cmd; + } + + } + + private static readonly CommonCommandFactory DefaultCommandFactory = new(); + + private sealed class CommandFactory0 : CommonCommandFactory // + { + internal static readonly CommandFactory0 Instance = new(); + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)!, Id = default(int) }); // expected shape + var ps = cmd.Parameters; + global::System.Data.Common.DbParameter p; + p = cmd.CreateParameter(); + p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, typed.Name); + ps.Add(p); + + p = cmd.CreateParameter(); + p.ParameterName = "Id"; + p.DbType = global::System.Data.DbType.Int32; + p.Direction = global::System.Data.ParameterDirection.Input; + p.Value = AsValue(typed.Id); + ps.Add(p); + + } + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)!, Id = default(int) }); // expected shape + var ps = cmd.Parameters; + ps[0].Value = AsValue(typed.Name); + ps[1].Value = AsValue(typed.Id); + + } + public override bool CanPrepare => true; + + } + + private sealed class CommandFactory1 : CommonCommandFactory + { + internal static readonly CommandFactory1 Instance = new(); + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.Poco args) + { + var ps = cmd.Parameters; + global::System.Data.Common.DbParameter p; + p = cmd.CreateParameter(); + p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, args.Name); + ps.Add(p); + + p = cmd.CreateParameter(); + p.ParameterName = "Id"; + p.DbType = global::System.Data.DbType.Int32; + p.Direction = global::System.Data.ParameterDirection.Input; + p.Value = AsValue(args.Id); + ps.Add(p); + + } + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.Poco args) + { + var ps = cmd.Parameters; + ps[0].Value = AsValue(args.Name); + ps[1].Value = AsValue(args.Id); + + } + public override bool CanPrepare => true; + + } + + + } +} +namespace System.Runtime.CompilerServices +{ + // this type is needed by the compiler to implement interceptors - it doesn't need to + // come from the runtime itself, though + + [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + sealed file class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) + { + _ = path; + _ = lineNumber; + _ = columnNumber; + } + } +} +namespace Dapper.Aot.Generated +{ + /// + /// Contains helpers to properly handle + /// + static class DbStringHelpers + { + public static global::System.Data.Common.DbParameter ConvertToDbParameter( + global::System.Data.Common.DbParameter dbParameter, + global::Dapper.DbString? dbString) + { + if (dbString is null) + { + dbParameter.Value = global::System.DBNull.Value; + return dbParameter; + } + + dbParameter.Size = dbString switch + { + { Value: null } => 0, + { IsAnsi: false } => global::System.Text.Encoding.ASCII.GetByteCount(dbString.Value), + { IsAnsi: true } => global::System.Text.Encoding.Default.GetByteCount(dbString.Value), + _ => default + }; + dbParameter.DbType = dbString switch + { + { IsAnsi: true, IsFixedLength: true } => global::System.Data.DbType.AnsiStringFixedLength, + { IsAnsi: true, IsFixedLength: false } => global::System.Data.DbType.AnsiString, + { IsAnsi: false, IsFixedLength: true } => global::System.Data.DbType.StringFixedLength, + { IsAnsi: false, IsFixedLength: false } => global::System.Data.DbType.String, + _ => dbParameter.DbType + }; + + dbParameter.Value = dbString.Value as object ?? global::System.DBNull.Value; + + return dbParameter; + } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.txt b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.txt new file mode 100644 index 00000000..29906b6b --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.txt @@ -0,0 +1,4 @@ +Generator produced 1 diagnostics: + +Hidden DAP000 L1 C1 +Dapper.AOT handled 2 of 2 possible call-sites using 2 interceptors, 2 commands and 0 readers diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs index 017f4a07..c0540ee2 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs @@ -78,8 +78,24 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 19, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync5(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 20, 24)] + internal static global::System.Collections.Generic.IEnumerable Query5(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, HasParameters, Buffered, Text, BindResultsByName, KnownParameters + // takes parameter: + // parameter map: Name + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is not null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory2.Instance).QueryBuffered(param, RowFactory0.Instance); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 31, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync6(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName // returns data: global::Foo.Customer @@ -92,8 +108,8 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 20, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync6(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 32, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync7(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, HasParameters, Buffered, StoredProcedure, BindResultsByName, KnownParameters // takes parameter: @@ -255,6 +271,30 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje } + private sealed class CommandFactory2 : CommonCommandFactory // + { + internal static readonly CommandFactory2 Instance = new(); + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)! }); // expected shape + var ps = cmd.Parameters; + global::System.Data.Common.DbParameter p; + p = cmd.CreateParameter(); + p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, typed.Name); + ps.Add(p); + + } + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) + { + var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)! }); // expected shape + var ps = cmd.Parameters; + ps[0].Value = AsValue(typed.Name); + + } + public override bool CanPrepare => true; + + } + } } @@ -274,4 +314,43 @@ public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber _ = columnNumber; } } +} +namespace Dapper.Aot.Generated +{ + /// + /// Contains helpers to properly handle + /// + static class DbStringHelpers + { + public static global::System.Data.Common.DbParameter ConvertToDbParameter( + global::System.Data.Common.DbParameter dbParameter, + global::Dapper.DbString? dbString) + { + if (dbString is null) + { + dbParameter.Value = global::System.DBNull.Value; + return dbParameter; + } + + dbParameter.Size = dbString switch + { + { Value: null } => 0, + { IsAnsi: false } => global::System.Text.Encoding.ASCII.GetByteCount(dbString.Value), + { IsAnsi: true } => global::System.Text.Encoding.Default.GetByteCount(dbString.Value), + _ => default + }; + dbParameter.DbType = dbString switch + { + { IsAnsi: true, IsFixedLength: true } => global::System.Data.DbType.AnsiStringFixedLength, + { IsAnsi: true, IsFixedLength: false } => global::System.Data.DbType.AnsiString, + { IsAnsi: false, IsFixedLength: true } => global::System.Data.DbType.StringFixedLength, + { IsAnsi: false, IsFixedLength: false } => global::System.Data.DbType.String, + _ => dbParameter.DbType + }; + + dbParameter.Value = dbString.Value as object ?? global::System.DBNull.Value; + + return dbParameter; + } + } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.txt b/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.txt index 11a24dec..ef6792ca 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.txt +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.txt @@ -1,4 +1,4 @@ Generator produced 1 diagnostics: Hidden DAP000 L1 C1 -Dapper.AOT handled 7 of 7 possible call-sites using 7 interceptors, 2 commands and 1 readers +Dapper.AOT handled 8 of 8 possible call-sites using 8 interceptors, 3 commands and 1 readers From 5526af49bddc8bedb4086745d672fa5781324e5f Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Wed, 1 May 2024 19:01:49 +0200 Subject: [PATCH 08/27] fix DAP44 error appeared in refactor --- .../CodeAnalysis/DapperAnalyzer.cs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs index 825b1417..cbf7336c 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs @@ -829,7 +829,15 @@ static void ValidateParameters(MemberMap? parameters, OperationFlags flags, Acti { if (parameters is null) return; - var isCancellationElement = IsCancellationToken(parameters.ElementType); + var usingVanillaDapperMode = flags.HasAny(OperationFlags.DoNotGenerate); // using vanilla Dapper mode + if (usingVanillaDapperMode) + { + if (parameters.Members.Any(s => s.IsCancellation) || IsCancellationToken(parameters.ElementType)) + { + onDiagnostic(Diagnostic.Create(Diagnostics.CancellationNotSupported, parameters.Location)); + } + } + var isFirstCancellation = true; foreach (var member in parameters.Members) { @@ -847,20 +855,10 @@ void ValidateDbStringParameter(ElementMember member) void ValidateCancellationTokenParameter(ElementMember member) { - if (flags.HasAny(OperationFlags.DoNotGenerate)) // using vanilla Dapper mode - { - if (isCancellationElement) - { - onDiagnostic(Diagnostic.Create(Diagnostics.CancellationNotSupported, parameters.Location)); - } - } - else + if (!usingVanillaDapperMode && member.IsCancellation) { - if (member.IsCancellation) - { - if (isFirstCancellation) isFirstCancellation = false; - else onDiagnostic(Diagnostic.Create(Diagnostics.CancellationDuplicated, member.GetLocation())); - } + if (isFirstCancellation) isFirstCancellation = false; + else onDiagnostic(Diagnostic.Create(Diagnostics.CancellationDuplicated, member.GetLocation())); } } } From 4c1b314a4739db3453fa417660eaa855b9803916 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Wed, 1 May 2024 19:17:25 +0200 Subject: [PATCH 09/27] pre-review fixes --- .../DapperInterceptorGenerator.cs | 9 +- test/Dapper.AOT.Test/Dapper.AOT.Test.csproj | 4 +- .../Interceptors/DbString.output.cs | 2 + .../Interceptors/Query.input.cs | 12 --- .../Interceptors/Query.output.cs | 95 ++----------------- .../Interceptors/Query.output.netfx.cs | 87 +---------------- .../Interceptors/Query.output.netfx.txt | 2 +- .../Interceptors/Query.output.txt | 2 +- 8 files changed, 20 insertions(+), 193 deletions(-) diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs index f314d49b..891c3a75 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs @@ -1075,7 +1075,8 @@ private static void WriteArgs(in GenerateState ctx, ITypeSymbol? parameterType, { case WriteArgsMode.Add: sb.Append("p = cmd.CreateParameter();").NewLine(); - + sb.Append("p.ParameterName = ").AppendVerbatimLiteral(member.DbName).Append(";").NewLine(); + if (member.DapperSpecialType is DapperSpecialType.DbString) { ctx.GeneratorContext.IncludeGenerationType(IncludedGeneration.DbStringHelpers); @@ -1087,7 +1088,6 @@ private static void WriteArgs(in GenerateState ctx, ITypeSymbol? parameterType, break; } - sb.Append("p.ParameterName = ").AppendVerbatimLiteral(member.DbName).Append(";").NewLine(); var dbType = member.GetDbType(out _); var size = member.TryGetValue("Size"); bool useSetValueWithDefaultSize = false; @@ -1195,11 +1195,6 @@ private static void WriteArgs(in GenerateState ctx, ITypeSymbol? parameterType, } } - static void CalculateDbSyze() - { - - } - static void AppendDbParameterSetting(CodeWriter sb, string memberName, int? value) { if (value is not null) diff --git a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj index 6245b025..67a758e5 100644 --- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj +++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj @@ -14,8 +14,8 @@ - - + + $([System.String]::Copy(%(Filename)).Replace('.output', '.input.cs')) diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs index e6c5231c..e8cab6e4 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs @@ -63,6 +63,7 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? var ps = cmd.Parameters; global::System.Data.Common.DbParameter p; p = cmd.CreateParameter(); + p.ParameterName = "Name"; p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, typed.Name); ps.Add(p); @@ -94,6 +95,7 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global: var ps = cmd.Parameters; global::System.Data.Common.DbParameter p; p = cmd.CreateParameter(); + p.ParameterName = "Name"; p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, args.Name); ps.Add(p); diff --git a/test/Dapper.AOT.Test/Interceptors/Query.input.cs b/test/Dapper.AOT.Test/Interceptors/Query.input.cs index a30bb7df..1a1e64f9 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.input.cs +++ b/test/Dapper.AOT.Test/Interceptors/Query.input.cs @@ -16,18 +16,6 @@ static async Task SomeCode(DbConnection connection, string bar, bool isBuffered) _ = connection.Query("def", buffered: false, commandType: CommandType.StoredProcedure); _ = connection.Query("def @Foo", obj, buffered: true, commandType: CommandType.Text); - // DbString - _ = connection.Query("select * from Foo where Name = @Name", new - { - Name = new DbString - { - Value = "MyCar", - IsFixedLength = false, - Length = 5, - IsAnsi = true - } - }); - _ = await connection.QueryAsync("def"); _ = await connection.QueryAsync("def", obj); #if !NETFRAMEWORK diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.cs b/test/Dapper.AOT.Test/Interceptors/Query.output.cs index a7fb54be..6e8f3a26 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.cs @@ -78,24 +78,8 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 20, 24)] - internal static global::System.Collections.Generic.IEnumerable Query5(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, HasParameters, Buffered, Text, BindResultsByName, KnownParameters - // takes parameter: - // parameter map: Name - // returns data: global::Foo.Customer - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is not null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory2.Instance).QueryBuffered(param, RowFactory0.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 31, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync6(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 19, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync5(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName // returns data: global::Foo.Customer @@ -108,8 +92,8 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 32, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync7(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 20, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync6(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, HasParameters, Buffered, StoredProcedure, BindResultsByName, KnownParameters // takes parameter: @@ -124,8 +108,8 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 34, 47)] - internal static global::System.Collections.Generic.IAsyncEnumerable QueryUnbufferedAsync8(this global::System.Data.Common.DbConnection cnn, string sql, object? param, global::System.Data.Common.DbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 22, 47)] + internal static global::System.Collections.Generic.IAsyncEnumerable QueryUnbufferedAsync7(this global::System.Data.Common.DbConnection cnn, string sql, object? param, global::System.Data.Common.DbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, Unbuffered, StoredProcedure, BindResultsByName // returns data: global::Foo.Customer @@ -137,8 +121,8 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 35, 47)] - internal static global::System.Collections.Generic.IAsyncEnumerable QueryUnbufferedAsync9(this global::System.Data.Common.DbConnection cnn, string sql, object? param, global::System.Data.Common.DbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 23, 47)] + internal static global::System.Collections.Generic.IAsyncEnumerable QueryUnbufferedAsync8(this global::System.Data.Common.DbConnection cnn, string sql, object? param, global::System.Data.Common.DbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, HasParameters, Unbuffered, Text, BindResultsByName, KnownParameters // takes parameter: @@ -299,30 +283,6 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje } - private sealed class CommandFactory2 : CommonCommandFactory // - { - internal static readonly CommandFactory2 Instance = new(); - public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) - { - var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)! }); // expected shape - var ps = cmd.Parameters; - global::System.Data.Common.DbParameter p; - p = cmd.CreateParameter(); - p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, typed.Name); - ps.Add(p); - - } - public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) - { - var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)! }); // expected shape - var ps = cmd.Parameters; - ps[0].Value = AsValue(typed.Name); - - } - public override bool CanPrepare => true; - - } - } } @@ -342,43 +302,4 @@ public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber _ = columnNumber; } } -} -namespace Dapper.Aot.Generated -{ - /// - /// Contains helpers to properly handle - /// - static class DbStringHelpers - { - public static global::System.Data.Common.DbParameter ConvertToDbParameter( - global::System.Data.Common.DbParameter dbParameter, - global::Dapper.DbString? dbString) - { - if (dbString is null) - { - dbParameter.Value = global::System.DBNull.Value; - return dbParameter; - } - - dbParameter.Size = dbString switch - { - { Value: null } => 0, - { IsAnsi: false } => global::System.Text.Encoding.ASCII.GetByteCount(dbString.Value), - { IsAnsi: true } => global::System.Text.Encoding.Default.GetByteCount(dbString.Value), - _ => default - }; - dbParameter.DbType = dbString switch - { - { IsAnsi: true, IsFixedLength: true } => global::System.Data.DbType.AnsiStringFixedLength, - { IsAnsi: true, IsFixedLength: false } => global::System.Data.DbType.AnsiString, - { IsAnsi: false, IsFixedLength: true } => global::System.Data.DbType.StringFixedLength, - { IsAnsi: false, IsFixedLength: false } => global::System.Data.DbType.String, - _ => dbParameter.DbType - }; - - dbParameter.Value = dbString.Value as object ?? global::System.DBNull.Value; - - return dbParameter; - } - } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs index c0540ee2..017f4a07 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs @@ -78,24 +78,8 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 20, 24)] - internal static global::System.Collections.Generic.IEnumerable Query5(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, TypedResult, HasParameters, Buffered, Text, BindResultsByName, KnownParameters - // takes parameter: - // parameter map: Name - // returns data: global::Foo.Customer - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); - global::System.Diagnostics.Debug.Assert(buffered is true); - global::System.Diagnostics.Debug.Assert(param is not null); - - return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory2.Instance).QueryBuffered(param, RowFactory0.Instance); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 31, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync6(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 19, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync5(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName // returns data: global::Foo.Customer @@ -108,8 +92,8 @@ file static class DapperGeneratedInterceptors } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 32, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync7(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Query.input.cs", 20, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync6(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, HasParameters, Buffered, StoredProcedure, BindResultsByName, KnownParameters // takes parameter: @@ -271,30 +255,6 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje } - private sealed class CommandFactory2 : CommonCommandFactory // - { - internal static readonly CommandFactory2 Instance = new(); - public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) - { - var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)! }); // expected shape - var ps = cmd.Parameters; - global::System.Data.Common.DbParameter p; - p = cmd.CreateParameter(); - p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, typed.Name); - ps.Add(p); - - } - public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) - { - var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)! }); // expected shape - var ps = cmd.Parameters; - ps[0].Value = AsValue(typed.Name); - - } - public override bool CanPrepare => true; - - } - } } @@ -314,43 +274,4 @@ public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber _ = columnNumber; } } -} -namespace Dapper.Aot.Generated -{ - /// - /// Contains helpers to properly handle - /// - static class DbStringHelpers - { - public static global::System.Data.Common.DbParameter ConvertToDbParameter( - global::System.Data.Common.DbParameter dbParameter, - global::Dapper.DbString? dbString) - { - if (dbString is null) - { - dbParameter.Value = global::System.DBNull.Value; - return dbParameter; - } - - dbParameter.Size = dbString switch - { - { Value: null } => 0, - { IsAnsi: false } => global::System.Text.Encoding.ASCII.GetByteCount(dbString.Value), - { IsAnsi: true } => global::System.Text.Encoding.Default.GetByteCount(dbString.Value), - _ => default - }; - dbParameter.DbType = dbString switch - { - { IsAnsi: true, IsFixedLength: true } => global::System.Data.DbType.AnsiStringFixedLength, - { IsAnsi: true, IsFixedLength: false } => global::System.Data.DbType.AnsiString, - { IsAnsi: false, IsFixedLength: true } => global::System.Data.DbType.StringFixedLength, - { IsAnsi: false, IsFixedLength: false } => global::System.Data.DbType.String, - _ => dbParameter.DbType - }; - - dbParameter.Value = dbString.Value as object ?? global::System.DBNull.Value; - - return dbParameter; - } - } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.txt b/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.txt index ef6792ca..11a24dec 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.txt +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.txt @@ -1,4 +1,4 @@ Generator produced 1 diagnostics: Hidden DAP000 L1 C1 -Dapper.AOT handled 8 of 8 possible call-sites using 8 interceptors, 3 commands and 1 readers +Dapper.AOT handled 7 of 7 possible call-sites using 7 interceptors, 2 commands and 1 readers diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.txt b/test/Dapper.AOT.Test/Interceptors/Query.output.txt index 7e65e705..55f86d5c 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.txt +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.txt @@ -1,4 +1,4 @@ Generator produced 1 diagnostics: Hidden DAP000 L1 C1 -Dapper.AOT handled 10 of 10 possible call-sites using 10 interceptors, 3 commands and 1 readers +Dapper.AOT handled 9 of 9 possible call-sites using 9 interceptors, 2 commands and 1 readers From 671cb09cfa3f38e10d88389f47a925511c8e5b1c Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Fri, 3 May 2024 11:28:54 +0200 Subject: [PATCH 10/27] change DbString configuration method declaration --- .../CodeAnalysis/DapperInterceptorGenerator.cs | 2 +- .../InGeneration/DapperHelpers.cs | 6 ++---- .../InGeneration/DbStringHelpersTests.cs | 2 +- test/Dapper.AOT.Test/Interceptors/DbString.output.cs | 10 ++++------ .../Interceptors/DbString.output.netfx.cs | 12 ++++++------ 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs index 891c3a75..bcc59115 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs @@ -1081,7 +1081,7 @@ private static void WriteArgs(in GenerateState ctx, ITypeSymbol? parameterType, { ctx.GeneratorContext.IncludeGenerationType(IncludedGeneration.DbStringHelpers); - sb.Append("p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, ") + sb.Append("global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(p, ") .Append(source).Append(".").Append(member.DbName).Append(");").NewLine(); sb.Append("ps.Add(p);").NewLine(); // dont forget to add parameter to command parameters collection diff --git a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs index f24d20f2..01fac534 100644 --- a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs +++ b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs @@ -5,14 +5,14 @@ /// static class DbStringHelpers { - public static global::System.Data.Common.DbParameter ConvertToDbParameter( + public static void ConfigureDbStringDbParameter( global::System.Data.Common.DbParameter dbParameter, global::Dapper.DbString? dbString) { if (dbString is null) { dbParameter.Value = global::System.DBNull.Value; - return dbParameter; + return; } dbParameter.Size = dbString switch @@ -32,8 +32,6 @@ static class DbStringHelpers }; dbParameter.Value = dbString.Value as object ?? global::System.DBNull.Value; - - return dbParameter; } } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs b/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs index 29d3b0b8..0e4dd170 100644 --- a/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs +++ b/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs @@ -24,7 +24,7 @@ public void ConfigureDbString_ShouldProperlySetupDbParameter(bool isAnsi, bool i Length = 60 }; - param = Aot.Generated.DbStringHelpers.ConvertToDbParameter(param, dbString); + Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(param, dbString); Assert.Equal(expectedSize, param.Size); Assert.Equal(expectedDbType, param.DbType); diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs index e8cab6e4..d52c4719 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs @@ -64,7 +64,7 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? global::System.Data.Common.DbParameter p; p = cmd.CreateParameter(); p.ParameterName = "Name"; - p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, typed.Name); + global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(p, typed.Name); ps.Add(p); p = cmd.CreateParameter(); @@ -96,7 +96,7 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global: global::System.Data.Common.DbParameter p; p = cmd.CreateParameter(); p.ParameterName = "Name"; - p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, args.Name); + global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(p, args.Name); ps.Add(p); p = cmd.CreateParameter(); @@ -145,14 +145,14 @@ namespace Dapper.Aot.Generated /// static class DbStringHelpers { - public static global::System.Data.Common.DbParameter ConvertToDbParameter( + public static void ConfigureDbStringDbParameter( global::System.Data.Common.DbParameter dbParameter, global::Dapper.DbString? dbString) { if (dbString is null) { dbParameter.Value = global::System.DBNull.Value; - return dbParameter; + return; } dbParameter.Size = dbString switch @@ -172,8 +172,6 @@ static class DbStringHelpers }; dbParameter.Value = dbString.Value as object ?? global::System.DBNull.Value; - - return dbParameter; } } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs index e6c5231c..d52c4719 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs @@ -63,7 +63,8 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? var ps = cmd.Parameters; global::System.Data.Common.DbParameter p; p = cmd.CreateParameter(); - p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, typed.Name); + p.ParameterName = "Name"; + global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(p, typed.Name); ps.Add(p); p = cmd.CreateParameter(); @@ -94,7 +95,8 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global: var ps = cmd.Parameters; global::System.Data.Common.DbParameter p; p = cmd.CreateParameter(); - p = global::Dapper.Aot.Generated.DbStringHelpers.ConvertToDbParameter(p, args.Name); + p.ParameterName = "Name"; + global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(p, args.Name); ps.Add(p); p = cmd.CreateParameter(); @@ -143,14 +145,14 @@ namespace Dapper.Aot.Generated /// static class DbStringHelpers { - public static global::System.Data.Common.DbParameter ConvertToDbParameter( + public static void ConfigureDbStringDbParameter( global::System.Data.Common.DbParameter dbParameter, global::Dapper.DbString? dbString) { if (dbString is null) { dbParameter.Value = global::System.DBNull.Value; - return dbParameter; + return; } dbParameter.Size = dbString switch @@ -170,8 +172,6 @@ static class DbStringHelpers }; dbParameter.Value = dbString.Value as object ?? global::System.DBNull.Value; - - return dbParameter; } } } \ No newline at end of file From 6a668b7e1775768599f8fcd484c25559491a962b Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Fri, 3 May 2024 12:09:39 +0200 Subject: [PATCH 11/27] address initial comments --- .../DapperInterceptorGenerator.cs | 12 +++++++++++ .../IncludedGenerationExtensions.cs | 7 +++++++ .../Writers/PreGeneratedCodeWriter.cs | 7 ++++--- .../InGeneration/DapperHelpers.cs | 16 +++++++++------ .../InGeneration/DbStringHelpersTests.cs | 8 ++++---- .../Interceptors/DbString.output.cs | 20 +++++++++++-------- .../Interceptors/DbString.output.netfx.cs | 20 +++++++++++-------- 7 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 src/Dapper.AOT.Analyzers/CodeAnalysis/Extensions/IncludedGenerationExtensions.cs diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs index bcc59115..702feb08 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs @@ -1161,6 +1161,18 @@ private static void WriteArgs(in GenerateState ctx, ITypeSymbol? parameterType, } break; case WriteArgsMode.Update: + if (member.DapperSpecialType is DapperSpecialType.DbString) + { + ctx.GeneratorContext.IncludeGenerationType(IncludedGeneration.DbStringHelpers); + + sb.Append("global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter") + .Append("(ps[").Append(parameterIndex).Append("], ") + .Append(source).Append(".").Append(member.CodeName) + .Append(");").NewLine(); + + break; + } + sb.Append("ps["); if ((flags & WriteArgsFlags.NeedsTest) != 0) sb.AppendVerbatimLiteral(member.DbName); else sb.Append(parameterIndex); diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/Extensions/IncludedGenerationExtensions.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/Extensions/IncludedGenerationExtensions.cs new file mode 100644 index 00000000..1e371f0f --- /dev/null +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/Extensions/IncludedGenerationExtensions.cs @@ -0,0 +1,7 @@ +namespace Dapper.CodeAnalysis.Extensions +{ + internal static class IncludedGenerationExtensions + { + public static bool HasAny(this IncludedGeneration value, IncludedGeneration flag) => (value & flag) != 0; + } +} diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/PreGeneratedCodeWriter.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/PreGeneratedCodeWriter.cs index 32e92e07..94f7891d 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/PreGeneratedCodeWriter.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/Writers/PreGeneratedCodeWriter.cs @@ -1,4 +1,5 @@ -using Dapper.Internal; +using Dapper.CodeAnalysis.Extensions; +using Dapper.Internal; using Microsoft.CodeAnalysis; namespace Dapper.CodeAnalysis.Writers @@ -18,12 +19,12 @@ public PreGeneratedCodeWriter( public void Write(IncludedGeneration includedGenerations) { - if (includedGenerations.HasFlag(IncludedGeneration.InterceptsLocationAttribute)) + if (includedGenerations.HasAny(IncludedGeneration.InterceptsLocationAttribute)) { WriteInterceptsLocationAttribute(); } - if (includedGenerations.HasFlag(IncludedGeneration.DbStringHelpers)) + if (includedGenerations.HasAny(IncludedGeneration.DbStringHelpers)) { _codeWriter.NewLine().Append(Resources.ReadString("Dapper.InGeneration.DapperHelpers.cs")); } diff --git a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs index 01fac534..4c987895 100644 --- a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs +++ b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs @@ -15,13 +15,17 @@ public static void ConfigureDbStringDbParameter( return; } - dbParameter.Size = dbString switch + // repeating logic from Dapper: + // https://github.com/DapperLib/Dapper/blob/52160dc44699ec7eb5ad57d0dddc6ded4662fcb9/Dapper/DbString.cs#L71 + if (dbString.Length == -1 && dbString.Value is not null && dbString.Value.Length <= global::Dapper.DbString.DefaultLength) { - { Value: null } => 0, - { IsAnsi: false } => global::System.Text.Encoding.ASCII.GetByteCount(dbString.Value), - { IsAnsi: true } => global::System.Text.Encoding.Default.GetByteCount(dbString.Value), - _ => default - }; + dbParameter.Size = global::Dapper.DbString.DefaultLength; + } + else + { + dbParameter.Size = dbString.Length; + } + dbParameter.DbType = dbString switch { { IsAnsi: true, IsFixedLength: true } => global::System.Data.DbType.AnsiStringFixedLength, diff --git a/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs b/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs index 0e4dd170..bcaa4e2c 100644 --- a/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs +++ b/test/Dapper.AOT.Test/InGeneration/DbStringHelpersTests.cs @@ -9,10 +9,10 @@ namespace Dapper.AOT.Test.InGeneration public class DbStringHelpersTests { [Theory] - [InlineData(false, false, "qweqwe", DbType.String, 6)] - [InlineData(false, true, "qweqwe", DbType.StringFixedLength, 6)] - [InlineData(true, false, "qweqwe", DbType.AnsiString, 6)] - [InlineData(true, true, "qweqwe", DbType.AnsiStringFixedLength, 6)] + [InlineData(false, false, "qweqwe", DbType.String, 60)] + [InlineData(false, true, "qweqwe", DbType.StringFixedLength, 60)] + [InlineData(true, false, "qweqwe", DbType.AnsiString, 60)] + [InlineData(true, true, "qweqwe", DbType.AnsiStringFixedLength, 60)] public void ConfigureDbString_ShouldProperlySetupDbParameter(bool isAnsi, bool isFixedLength, string dbStringValue, DbType expectedDbType, int expectedSize) { var param = CreateDbParameter(); diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs index d52c4719..98e93105 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs @@ -79,7 +79,7 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje { var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)!, Id = default(int) }); // expected shape var ps = cmd.Parameters; - ps[0].Value = AsValue(typed.Name); + global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(ps[0], typed.Name); ps[1].Value = AsValue(typed.Id); } @@ -110,7 +110,7 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global: public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.Poco args) { var ps = cmd.Parameters; - ps[0].Value = AsValue(args.Name); + global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(ps[0], args.Name); ps[1].Value = AsValue(args.Id); } @@ -155,13 +155,17 @@ public static void ConfigureDbStringDbParameter( return; } - dbParameter.Size = dbString switch + // repeating logic from Dapper: + // https://github.com/DapperLib/Dapper/blob/52160dc44699ec7eb5ad57d0dddc6ded4662fcb9/Dapper/DbString.cs#L71 + if (dbString.Length == -1 && dbString.Value is not null && dbString.Value.Length <= global::Dapper.DbString.DefaultLength) { - { Value: null } => 0, - { IsAnsi: false } => global::System.Text.Encoding.ASCII.GetByteCount(dbString.Value), - { IsAnsi: true } => global::System.Text.Encoding.Default.GetByteCount(dbString.Value), - _ => default - }; + dbParameter.Size = global::Dapper.DbString.DefaultLength; + } + else + { + dbParameter.Size = dbString.Length; + } + dbParameter.DbType = dbString switch { { IsAnsi: true, IsFixedLength: true } => global::System.Data.DbType.AnsiStringFixedLength, diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs index d52c4719..98e93105 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs @@ -79,7 +79,7 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje { var typed = Cast(args, static () => new { Name = default(global::Dapper.DbString)!, Id = default(int) }); // expected shape var ps = cmd.Parameters; - ps[0].Value = AsValue(typed.Name); + global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(ps[0], typed.Name); ps[1].Value = AsValue(typed.Id); } @@ -110,7 +110,7 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global: public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.Poco args) { var ps = cmd.Parameters; - ps[0].Value = AsValue(args.Name); + global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(ps[0], args.Name); ps[1].Value = AsValue(args.Id); } @@ -155,13 +155,17 @@ public static void ConfigureDbStringDbParameter( return; } - dbParameter.Size = dbString switch + // repeating logic from Dapper: + // https://github.com/DapperLib/Dapper/blob/52160dc44699ec7eb5ad57d0dddc6ded4662fcb9/Dapper/DbString.cs#L71 + if (dbString.Length == -1 && dbString.Value is not null && dbString.Value.Length <= global::Dapper.DbString.DefaultLength) { - { Value: null } => 0, - { IsAnsi: false } => global::System.Text.Encoding.ASCII.GetByteCount(dbString.Value), - { IsAnsi: true } => global::System.Text.Encoding.Default.GetByteCount(dbString.Value), - _ => default - }; + dbParameter.Size = global::Dapper.DbString.DefaultLength; + } + else + { + dbParameter.Size = dbString.Length; + } + dbParameter.DbType = dbString switch { { IsAnsi: true, IsFixedLength: true } => global::System.Data.DbType.AnsiStringFixedLength, From b261d12f5c26a37a74b30562f47ae503dde24ca3 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Fri, 24 May 2024 11:09:08 +0200 Subject: [PATCH 12/27] DbStringHelpers to file class --- src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs index 4c987895..b3fe2893 100644 --- a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs +++ b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs @@ -3,7 +3,7 @@ /// /// Contains helpers to properly handle /// - static class DbStringHelpers + file static class DbStringHelpers { public static void ConfigureDbStringDbParameter( global::System.Data.Common.DbParameter dbParameter, From 6a664a2f90551f49b9517cc79495b1b00f771971 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Fri, 24 May 2024 11:12:59 +0200 Subject: [PATCH 13/27] include `file` via preprocessor directive --- src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs | 5 ++++- test/Dapper.AOT.Test/Interceptors/DbString.output.cs | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs index b3fe2893..217d9da8 100644 --- a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs +++ b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs @@ -3,7 +3,10 @@ /// /// Contains helpers to properly handle /// - file static class DbStringHelpers +#if !DAPPERAOT_INTERNAL + file +#endif + static class DbStringHelpers { public static void ConfigureDbStringDbParameter( global::System.Data.Common.DbParameter dbParameter, diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs index 98e93105..fa609c25 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs @@ -143,6 +143,9 @@ namespace Dapper.Aot.Generated /// /// Contains helpers to properly handle /// +#if !DAPPERAOT_INTERNAL + file +#endif static class DbStringHelpers { public static void ConfigureDbStringDbParameter( From 9343ce1d7858fee9cb7241882bad504498b0b3bf Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 9 Jun 2024 23:08:56 +0200 Subject: [PATCH 14/27] only in dapper AOT --- .../CodeAnalysis/DapperAnalyzer.cs | 6 ++ test/Dapper.AOT.Test/Verifiers/DAP048.cs | 73 +++++++++---------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs index cbf7336c..3b2681fd 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs @@ -847,6 +847,12 @@ static void ValidateParameters(MemberMap? parameters, OperationFlags flags, Acti void ValidateDbStringParameter(ElementMember member) { + if (usingVanillaDapperMode) + { + // reporting ONLY in Dapper AOT + return; + } + if (member.DapperSpecialType == DapperSpecialType.DbString) { onDiagnostic(Diagnostic.Create(Diagnostics.MoveFromDbString, member.GetLocation())); diff --git a/test/Dapper.AOT.Test/Verifiers/DAP048.cs b/test/Dapper.AOT.Test/Verifiers/DAP048.cs index 8da724bf..886a6574 100644 --- a/test/Dapper.AOT.Test/Verifiers/DAP048.cs +++ b/test/Dapper.AOT.Test/Verifiers/DAP048.cs @@ -8,46 +8,43 @@ namespace Dapper.AOT.Test.Verifiers; public class DAP048 : Verifier { [Theory] - [InlineData("[DapperAot(false)]")] - [InlineData("[DapperAot(true)]")] - public Task MoveFromDbString(string dapperAotAttribute) => CSVerifyAsync($$""" - using Dapper; - using System; - using System.Linq; - using System.Data.Common; - using System.Threading; - using System.Threading.Tasks; + [InlineData(false)] + [InlineData(true)] + public Task MoveFromDbString(bool dapperAotEnabled) => CSVerifyAsync($$""" + using Dapper; + using System; + using System.Linq; + using System.Data.Common; + using System.Threading; + using System.Threading.Tasks; - {{dapperAotAttribute}} - class WithoutAot - { - public void DapperCode(DbConnection conn) - { - var sql = "SELECT * FROM Cars WHERE Name = @Name;"; - var cars = conn.Query(sql, - new - { - {|#0:Name|} = new DbString - { - Value = "MyCar", - IsFixedLength = false, - Length = 5, - IsAnsi = true - } - }); - } - } + [DapperAot({{dapperAotEnabled.ToString().ToLower()}})] + class WithoutAot + { + public void DapperCode(DbConnection conn) + { + var sql = "SELECT * FROM Cars WHERE Name = @Name;"; + var cars = conn.Query(sql, + new + { + {|#0:Name|} = new DbString + { + Value = "MyCar", + IsFixedLength = false, + Length = 5, + IsAnsi = true + } + }); + } + } - public class Car - { - public string Name { get; set; } - public int Speed { get; set; } - } - """, + public class Car + { + public string Name { get; set; } + public int Speed { get; set; } + } + """, DefaultConfig, - [ - Diagnostic(Diagnostics.MoveFromDbString) - .WithLocation(0) - ] + dapperAotEnabled ? [ Diagnostic(Diagnostics.MoveFromDbString).WithLocation(0) ] : [] ); } \ No newline at end of file From e80464847eb93880d6b4c36227a32897281b32f8 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Mon, 24 Jun 2024 14:21:28 +0200 Subject: [PATCH 15/27] move to Dapper.AOT.Test.Integration.csproj --- Dapper.AOT.sln | 7 +++ .../BatchPostgresql.cs | 1 + .../BatchTests.cs | 1 + .../BulkInsertIngegrationTests.cs | 1 + .../Dapper.AOT.Test.Integration.csproj | 54 +++++++++++++++++++ .../DbStringTests.cs | 30 +++++++++++ .../DynamicTests.cs | 1 + .../Helpers/XUnitSkippable.cs | 0 .../ManualGridReaderTests.cs | 6 +-- .../QueryTests.cs | 1 + .../Setup}/PostgresqlFixture.cs | 6 +-- .../Setup}/SqlClientFixture.cs | 6 +-- test/Dapper.AOT.Test/Dapper.AOT.Test.csproj | 2 +- .../Interceptors/DbString.output.netfx.cs | 3 ++ 14 files changed, 109 insertions(+), 10 deletions(-) rename test/{Dapper.AOT.Test/Integration => Dapper.AOT.Test.Integration}/BatchPostgresql.cs (98%) rename test/{Dapper.AOT.Test/Integration => Dapper.AOT.Test.Integration}/BatchTests.cs (99%) rename test/{Dapper.AOT.Test/Integration => Dapper.AOT.Test.Integration}/BulkInsertIngegrationTests.cs (99%) create mode 100644 test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj create mode 100644 test/Dapper.AOT.Test.Integration/DbStringTests.cs rename test/{Dapper.AOT.Test/Integration => Dapper.AOT.Test.Integration}/DynamicTests.cs (95%) rename test/{Dapper.AOT.Test => Dapper.AOT.Test.Integration}/Helpers/XUnitSkippable.cs (100%) rename test/{Dapper.AOT.Test/Integration => Dapper.AOT.Test.Integration}/ManualGridReaderTests.cs (98%) rename test/{Dapper.AOT.Test/Integration => Dapper.AOT.Test.Integration}/QueryTests.cs (99%) rename test/{Dapper.AOT.Test/Integration => Dapper.AOT.Test.Integration/Setup}/PostgresqlFixture.cs (93%) rename test/{Dapper.AOT.Test/Integration => Dapper.AOT.Test.Integration/Setup}/SqlClientFixture.cs (96%) diff --git a/Dapper.AOT.sln b/Dapper.AOT.sln index 15a5e283..17d61ea5 100644 --- a/Dapper.AOT.sln +++ b/Dapper.AOT.sln @@ -40,6 +40,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UsageVanilla", "test\UsageVanilla\UsageVanilla.csproj", "{840EA1CA-62FF-409E-89F5-CD3BB269BAE3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.AOT.Test.Integration", "test\Dapper.AOT.Test.Integration\Dapper.AOT.Test.Integration.csproj", "{63423B18-04F1-4571-ABB2-8D0925B0C3EB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -78,6 +80,10 @@ Global {840EA1CA-62FF-409E-89F5-CD3BB269BAE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {840EA1CA-62FF-409E-89F5-CD3BB269BAE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {840EA1CA-62FF-409E-89F5-CD3BB269BAE3}.Release|Any CPU.Build.0 = Release|Any CPU + {63423B18-04F1-4571-ABB2-8D0925B0C3EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63423B18-04F1-4571-ABB2-8D0925B0C3EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63423B18-04F1-4571-ABB2-8D0925B0C3EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63423B18-04F1-4571-ABB2-8D0925B0C3EB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -91,6 +97,7 @@ Global {A77B633C-573E-43CD-85A4-8063B33143B4} = {FE215D4B-811B-47BB-9F05-6382DD1C6729} {C6527566-38F4-43CC-9E0E-91C4B8854774} = {1135D4FD-770E-41DF-920B-A8F75E42A832} {840EA1CA-62FF-409E-89F5-CD3BB269BAE3} = {9A846B95-90CE-4335-9043-48C5B8EA4FB8} + {63423B18-04F1-4571-ABB2-8D0925B0C3EB} = {9A846B95-90CE-4335-9043-48C5B8EA4FB8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A89CDAFA-494F-4168-9648-1138BA738D43} diff --git a/test/Dapper.AOT.Test/Integration/BatchPostgresql.cs b/test/Dapper.AOT.Test.Integration/BatchPostgresql.cs similarity index 98% rename from test/Dapper.AOT.Test/Integration/BatchPostgresql.cs rename to test/Dapper.AOT.Test.Integration/BatchPostgresql.cs index 55fb266c..274ca64b 100644 --- a/test/Dapper.AOT.Test/Integration/BatchPostgresql.cs +++ b/test/Dapper.AOT.Test.Integration/BatchPostgresql.cs @@ -1,5 +1,6 @@ using System.Data; using System.Linq; +using Dapper.AOT.Test.Integration.Setup; using Xunit; namespace Dapper.AOT.Test.Integration; diff --git a/test/Dapper.AOT.Test/Integration/BatchTests.cs b/test/Dapper.AOT.Test.Integration/BatchTests.cs similarity index 99% rename from test/Dapper.AOT.Test/Integration/BatchTests.cs rename to test/Dapper.AOT.Test.Integration/BatchTests.cs index d154bdce..8ca88475 100644 --- a/test/Dapper.AOT.Test/Integration/BatchTests.cs +++ b/test/Dapper.AOT.Test.Integration/BatchTests.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.Data; using System.Linq; +using Dapper.AOT.Test.Integration.Setup; using Xunit; namespace Dapper.AOT.Test.Integration; diff --git a/test/Dapper.AOT.Test/Integration/BulkInsertIngegrationTests.cs b/test/Dapper.AOT.Test.Integration/BulkInsertIngegrationTests.cs similarity index 99% rename from test/Dapper.AOT.Test/Integration/BulkInsertIngegrationTests.cs rename to test/Dapper.AOT.Test.Integration/BulkInsertIngegrationTests.cs index 4d827379..706c724f 100644 --- a/test/Dapper.AOT.Test/Integration/BulkInsertIngegrationTests.cs +++ b/test/Dapper.AOT.Test.Integration/BulkInsertIngegrationTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Dapper.AOT.Test.Integration.Setup; using Xunit; namespace Dapper.AOT.Test.Integration; diff --git a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj new file mode 100644 index 00000000..755afdfa --- /dev/null +++ b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj @@ -0,0 +1,54 @@ + + + + net6.0;net48 + Dapper.AOT.Test.Integration + $(DefineConstants);DAPPERAOT_INTERNAL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + + + + + diff --git a/test/Dapper.AOT.Test.Integration/DbStringTests.cs b/test/Dapper.AOT.Test.Integration/DbStringTests.cs new file mode 100644 index 00000000..0e185aa0 --- /dev/null +++ b/test/Dapper.AOT.Test.Integration/DbStringTests.cs @@ -0,0 +1,30 @@ +using System.Linq; +using Dapper.AOT.Test.Integration.Setup; +using Xunit; + +namespace Dapper.AOT.Test.Integration; + +[Collection(SharedPostgresqlClient.Collection)] +public class DbStringTests +{ + private PostgresqlFixture _fixture; + + public DbStringTests(PostgresqlFixture fixture) + { + _fixture = fixture; + fixture.NpgsqlConnection.Execute(""" + CREATE TABLE IF NOT EXISTS dbStringTable( + id integer PRIMARY KEY, + name varchar(40) NOT NULL CHECK (name <> '') + ); + TRUNCATE dbStringTable; + """ + ); + } + + [Fact] + public void ExecuteMulti() + { + + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Integration/DynamicTests.cs b/test/Dapper.AOT.Test.Integration/DynamicTests.cs similarity index 95% rename from test/Dapper.AOT.Test/Integration/DynamicTests.cs rename to test/Dapper.AOT.Test.Integration/DynamicTests.cs index 46553881..7e18b7fa 100644 --- a/test/Dapper.AOT.Test/Integration/DynamicTests.cs +++ b/test/Dapper.AOT.Test.Integration/DynamicTests.cs @@ -1,6 +1,7 @@ using Microsoft.Data.SqlClient; using System; using System.Collections.Generic; +using Dapper.AOT.Test.Integration.Setup; using Xunit; namespace Dapper.AOT.Test.Integration; diff --git a/test/Dapper.AOT.Test/Helpers/XUnitSkippable.cs b/test/Dapper.AOT.Test.Integration/Helpers/XUnitSkippable.cs similarity index 100% rename from test/Dapper.AOT.Test/Helpers/XUnitSkippable.cs rename to test/Dapper.AOT.Test.Integration/Helpers/XUnitSkippable.cs diff --git a/test/Dapper.AOT.Test/Integration/ManualGridReaderTests.cs b/test/Dapper.AOT.Test.Integration/ManualGridReaderTests.cs similarity index 98% rename from test/Dapper.AOT.Test/Integration/ManualGridReaderTests.cs rename to test/Dapper.AOT.Test.Integration/ManualGridReaderTests.cs index eade42b9..c4861da4 100644 --- a/test/Dapper.AOT.Test/Integration/ManualGridReaderTests.cs +++ b/test/Dapper.AOT.Test.Integration/ManualGridReaderTests.cs @@ -1,10 +1,10 @@ -using Microsoft.Data.SqlClient; -using System; +using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.Data.SqlClient; using Xunit; -namespace Dapper.AOT.Test.Integration; +namespace Dapper.AOT.Test.Integration.Setup; [Collection(SharedSqlClient.Collection)] public class ManualGridReaderTests : IDisposable diff --git a/test/Dapper.AOT.Test/Integration/QueryTests.cs b/test/Dapper.AOT.Test.Integration/QueryTests.cs similarity index 99% rename from test/Dapper.AOT.Test/Integration/QueryTests.cs rename to test/Dapper.AOT.Test.Integration/QueryTests.cs index 7241759c..3f660a23 100644 --- a/test/Dapper.AOT.Test/Integration/QueryTests.cs +++ b/test/Dapper.AOT.Test.Integration/QueryTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Threading.Tasks; +using Dapper.AOT.Test.Integration.Setup; using Xunit; namespace Dapper.AOT.Test.Integration; diff --git a/test/Dapper.AOT.Test/Integration/PostgresqlFixture.cs b/test/Dapper.AOT.Test.Integration/Setup/PostgresqlFixture.cs similarity index 93% rename from test/Dapper.AOT.Test/Integration/PostgresqlFixture.cs rename to test/Dapper.AOT.Test.Integration/Setup/PostgresqlFixture.cs index 59329192..1023b18b 100644 --- a/test/Dapper.AOT.Test/Integration/PostgresqlFixture.cs +++ b/test/Dapper.AOT.Test.Integration/Setup/PostgresqlFixture.cs @@ -1,9 +1,9 @@ -using Npgsql; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Npgsql; using Testcontainers.PostgreSql; using Xunit; -namespace Dapper.AOT.Test.Integration; +namespace Dapper.AOT.Test.Integration.Setup; [CollectionDefinition(Collection)] public class SharedPostgresqlClient : ICollectionFixture diff --git a/test/Dapper.AOT.Test/Integration/SqlClientFixture.cs b/test/Dapper.AOT.Test.Integration/Setup/SqlClientFixture.cs similarity index 96% rename from test/Dapper.AOT.Test/Integration/SqlClientFixture.cs rename to test/Dapper.AOT.Test.Integration/Setup/SqlClientFixture.cs index 860d85dc..da22c4b8 100644 --- a/test/Dapper.AOT.Test/Integration/SqlClientFixture.cs +++ b/test/Dapper.AOT.Test.Integration/Setup/SqlClientFixture.cs @@ -1,8 +1,8 @@ -using Microsoft.Data.SqlClient; -using System; +using System; +using Microsoft.Data.SqlClient; using Xunit; -namespace Dapper.AOT.Test.Integration; +namespace Dapper.AOT.Test.Integration.Setup; [CollectionDefinition(Collection)] public class SharedSqlClient : ICollectionFixture diff --git a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj index 67a758e5..c96c32a3 100644 --- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj +++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj @@ -26,8 +26,8 @@ $([System.String]::Copy(%(Filename)).Replace('.VB.cs', '.cs')) - + diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs index 98e93105..fa609c25 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs @@ -143,6 +143,9 @@ namespace Dapper.Aot.Generated /// /// Contains helpers to properly handle /// +#if !DAPPERAOT_INTERNAL + file +#endif static class DbStringHelpers { public static void ConfigureDbStringDbParameter( From 63fd1a95bbe6672823ad19b959d187c499781bb7 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 30 Jun 2024 22:07:13 +0200 Subject: [PATCH 16/27] setup the infra for test --- .../InGeneration/DapperHelpers.cs | 2 +- .../Dapper.AOT.Test.Integration.csproj | 20 +-- .../DbStringTests.cs | 24 +-- .../InterceptionExecutables/DbString.cs | 20 +++ .../InterceptionExecutables/_Template.cs | 18 +++ .../InterceptedCodeExecutionTestsBase.cs | 140 ++++++++++++++++++ test/Dapper.AOT.Test/GeneratorTestBase.cs | 25 +++- .../TestCommon/RoslynTestHelpers.cs | 39 ++++- 8 files changed, 256 insertions(+), 32 deletions(-) create mode 100644 test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs create mode 100644 test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs create mode 100644 test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs diff --git a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs index 217d9da8..dbc9e5c0 100644 --- a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs +++ b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs @@ -6,7 +6,7 @@ #if !DAPPERAOT_INTERNAL file #endif - static class DbStringHelpers + static partial class DbStringHelpers { public static void ConfigureDbStringDbParameter( global::System.Data.Common.DbParameter dbParameter, diff --git a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj index 755afdfa..d1013c01 100644 --- a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj +++ b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj @@ -7,16 +7,17 @@ - - - - - - - - - + + + + + + + + + + @@ -49,6 +50,7 @@ + diff --git a/test/Dapper.AOT.Test.Integration/DbStringTests.cs b/test/Dapper.AOT.Test.Integration/DbStringTests.cs index 0e185aa0..829782e5 100644 --- a/test/Dapper.AOT.Test.Integration/DbStringTests.cs +++ b/test/Dapper.AOT.Test.Integration/DbStringTests.cs @@ -1,30 +1,32 @@ -using System.Linq; +using System.Data; +using System.Threading.Tasks; using Dapper.AOT.Test.Integration.Setup; using Xunit; +using Xunit.Abstractions; namespace Dapper.AOT.Test.Integration; [Collection(SharedPostgresqlClient.Collection)] -public class DbStringTests +public class DbStringTests : InterceptedCodeExecutionTestsBase { - private PostgresqlFixture _fixture; - - public DbStringTests(PostgresqlFixture fixture) + public DbStringTests(PostgresqlFixture fixture, ITestOutputHelper log) : base(fixture, log) { - _fixture = fixture; - fixture.NpgsqlConnection.Execute(""" + Fixture.NpgsqlConnection.Execute(""" CREATE TABLE IF NOT EXISTS dbStringTable( id integer PRIMARY KEY, name varchar(40) NOT NULL CHECK (name <> '') ); TRUNCATE dbStringTable; - """ - ); + """); } [Fact] - public void ExecuteMulti() + [DapperAot] + public async Task Test() { - + var sourceCode = PrepareSourceCodeFromFile("DbString"); + var executionResults = BuildAndExecuteInterceptedUserCode(sourceCode, methodName: "ExecuteAsync"); + + // TODO DO THE CHECK HERE } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs new file mode 100644 index 00000000..aae57a2f --- /dev/null +++ b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs @@ -0,0 +1,20 @@ +using System.Data; +using System.Data.SqlClient; +using System.Linq; + +namespace InterceptionExecutables +{ + using System; + using System.IO; + using Dapper; + using System.Threading.Tasks; + + public static class Program + { + public static async Task ExecuteAsync(IDbConnection dbConnection) + { + var res = await dbConnection.QueryAsync("SELECT count(*) FROM dbStringTable"); + return res.First(); + } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs new file mode 100644 index 00000000..9df40238 --- /dev/null +++ b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs @@ -0,0 +1,18 @@ +namespace InterceptionExecutables +{ + using System; + using System.IO; + using Dapper; + using System.Threading.Tasks; + + // this is just a sample for easy test-writing + public static class Program + { + public static async Task ExecuteAsync(IDbConnection dbConnection) + { + + } + } + + +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs new file mode 100644 index 00000000..1b5408a3 --- /dev/null +++ b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Dapper.CodeAnalysis; +using Dapper.TestCommon; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Dapper.AOT.Test.Integration.Setup; + +public abstract class InterceptedCodeExecutionTestsBase : GeneratorTestBase +{ + protected readonly PostgresqlFixture Fixture; + + protected InterceptedCodeExecutionTestsBase(PostgresqlFixture fixture, ITestOutputHelper? log) : base(log) + { + Fixture = fixture; + } + + protected static string PrepareSourceCodeFromFile(string inputFileName, string extension = ".cs") + { + var fullPath = Path.Combine("InterceptionExecutables", inputFileName + extension); + if (!File.Exists(fullPath)) + { + throw new FileNotFoundException(fullPath); + } + + using var sr = new StreamReader(fullPath); + return sr.ReadToEnd(); + } + + protected T BuildAndExecuteInterceptedUserCode( + string userSourceCode, + string className = "Program", + string methodName = "ExecuteAsync") + { + var inputCompilation = RoslynTestHelpers.CreateCompilation("Assembly", syntaxTrees: [ + BuildInterceptorSupportedSyntaxTree(filename: "Program.cs", userSourceCode) + ]); + + var diagnosticsOutputStringBuilder = new StringBuilder(); + var (compilation, generatorDriverRunResult, diagnostics, errorCount) = Execute(inputCompilation, diagnosticsOutputStringBuilder, initializer: g => + { + g.Log += message => Log(message); + }); + + var results = Assert.Single(generatorDriverRunResult.Results); + Assert.NotNull(compilation); + Assert.True(errorCount == 0, "User code should not report errors"); + + var assembly = Compile(compilation!); + var type = assembly.GetTypes().Single(t => t.FullName == $"InterceptionExecutables.{className}"); + var mainMethod = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); + Assert.NotNull(mainMethod); + + var result = mainMethod!.Invoke(obj: null, [ Fixture.NpgsqlConnection ]); + Assert.NotNull(result); + if (result is not Task taskResult) + { + throw new XunitException($"expected execution result is '{typeof(Task)}' but got {result!.GetType()}"); + } + + return taskResult.GetAwaiter().GetResult(); + } + + SyntaxTree BuildInterceptorSupportedSyntaxTree(string filename, string text) + { + var options = new CSharpParseOptions(LanguageVersion.Preview) + .WithFeatures(new [] + { + new KeyValuePair("InterceptorsPreviewNamespaces", "$(InterceptorsPreviewNamespaces);ProgramNamespace;Dapper.AOT"), + new KeyValuePair("Features", "InterceptorsPreview"), + new KeyValuePair("LangVersion", "preview"), + }); + + var stringText = SourceText.From(text, Encoding.UTF8); + return SyntaxFactory.ParseSyntaxTree(stringText, options, filename); + } + + static Assembly Compile(Compilation compilation) + { + using var peStream = new MemoryStream(); + using var pdbstream = new MemoryStream(); + + var dbg = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DebugInformationFormat.Pdb : DebugInformationFormat.PortablePdb; + var emitResult = compilation.Emit(peStream, pdbstream, null, null, null, new EmitOptions(false, dbg)); + if (!emitResult.Success) + { + TryThrowErrors(emitResult.Diagnostics); + } + + peStream.Position = pdbstream.Position = 0; + return Assembly.Load(peStream.ToArray(), pdbstream.ToArray()); + } + + static void TryThrowErrors(IEnumerable items) + { + var errors = new List(); + foreach (var item in items) + { + if (item.Severity == DiagnosticSeverity.Error) + { + errors.Add(item.GetMessage(CultureInfo.InvariantCulture)); + } + } + + if (errors.Count > 0) + { + throw new CompilationException(errors); + } + } + + class CompilationException : Exception + { + public IEnumerable Errors { get; private set; } + + public CompilationException(IEnumerable errors) + : base(string.Join(Environment.NewLine, errors)) + { + this.Errors = errors; + } + + public CompilationException(params string[] errors) + : base(string.Join(Environment.NewLine, errors)) + { + this.Errors = errors; + } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/GeneratorTestBase.cs b/test/Dapper.AOT.Test/GeneratorTestBase.cs index bf5d86f1..acec3af9 100644 --- a/test/Dapper.AOT.Test/GeneratorTestBase.cs +++ b/test/Dapper.AOT.Test/GeneratorTestBase.cs @@ -31,13 +31,27 @@ protected GeneratorTestBase(ITestOutputHelper? log) protected static string? GetOriginCodeLocation([CallerFilePath] string? path = null) => path; // input from https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md#unit-testing-of-generators - + protected (Compilation? Compilation, GeneratorDriverRunResult Result, ImmutableArray Diagnostics, int ErrorCount) Execute(string source, StringBuilder? diagnosticsTo = null, [CallerMemberName] string? name = null, string? fileName = null, Action? initializer = null ) where T : class, IIncrementalGenerator, new() + { + // Create the 'input' compilation that the generator will act on + if (string.IsNullOrWhiteSpace(name)) name = "compilation"; + if (string.IsNullOrWhiteSpace(fileName)) fileName = "input.cs"; + var inputCompilation = RoslynTestHelpers.CreateCompilation(source, name!, fileName!); + + return Execute(inputCompilation, diagnosticsTo, initializer); + } + + protected (Compilation? Compilation, GeneratorDriverRunResult Result, ImmutableArray Diagnostics, int ErrorCount) Execute( + Compilation inputCompilation, + StringBuilder? diagnosticsTo = null, + Action? initializer = null + ) where T : class, IIncrementalGenerator, new() { void OutputDiagnostic(Diagnostic d) { @@ -54,18 +68,13 @@ void Output(string message, bool force = false) diagnosticsTo?.AppendLine(message.Replace('\\', '/')); // need to normalize paths } } - // Create the 'input' compilation that the generator will act on - if (string.IsNullOrWhiteSpace(name)) name = "compilation"; - if (string.IsNullOrWhiteSpace(fileName)) fileName = "input.cs"; - Compilation inputCompilation = RoslynTestHelpers.CreateCompilation(source, name!, fileName!); - // directly create an instance of the generator // (Note: in the compiler this is loaded from an assembly, and created via reflection at runtime) T generator = new(); initializer?.Invoke(generator); ShowDiagnostics("Input code", inputCompilation, diagnosticsTo, "CS8795", "CS1701", "CS1702"); - + // Create the driver that will control the generation, passing in our generator GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { generator.AsSourceGenerator() }, parseOptions: RoslynTestHelpers.ParseOptionsLatestLangVer); @@ -73,7 +82,7 @@ void Output(string message, bool force = false) // (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls) driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); var runResult = driver.GetRunResult(); - + foreach (var result in runResult.Results) { if (result.Exception is not null) throw result.Exception; diff --git a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs index 02172345..abb8d53a 100644 --- a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs +++ b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs @@ -16,7 +16,7 @@ namespace Dapper.TestCommon; -internal static class RoslynTestHelpers +public static class RoslynTestHelpers { internal static readonly CSharpParseOptions ParseOptionsLatestLangVer = CSharpParseOptions.Default .WithLanguageVersion(LanguageVersion.Latest) @@ -45,8 +45,41 @@ internal static class RoslynTestHelpers }) .WithFeatures(new[] { DapperInterceptorGenerator.FeatureKeys.InterceptorsPreviewNamespacePair }); - public static Compilation CreateCompilation(string source, string name, string fileName) - => CSharpCompilation.Create(name, + public static Compilation CreateCompilation(string assemblyName, SyntaxTree[] syntaxTrees, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) + => CSharpCompilation.Create(assemblyName, + syntaxTrees: syntaxTrees, + references: new[] { + MetadataReference.CreateFromFile(typeof(Binder).Assembly.Location), +#if !NET48 + MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), + MetadataReference.CreateFromFile(Assembly.Load("System.Data").Location), + MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location), + MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location), + MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.Schema.ColumnAttribute).Assembly.Location), +#endif + MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DbConnection).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.Data.SqlClient.SqlConnection).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Microsoft.Data.SqlClient.SqlConnection).Assembly.Location), + MetadataReference.CreateFromFile(typeof(OracleConnection).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ValueTask).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Component).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DapperAotExtensions).Assembly.Location), + MetadataReference.CreateFromFile(typeof(SqlMapper).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ImmutableList).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), + MetadataReference.CreateFromFile(typeof(IAsyncEnumerable).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Span).Assembly.Location), + MetadataReference.CreateFromFile(typeof(IgnoreDataMemberAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(SqlMapper).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DynamicAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(IValidatableObject).Assembly.Location), + }, + options: new CSharpCompilationOptions(outputKind, allowUnsafe: true)); + + public static Compilation CreateCompilation(string source, string assemblyName, string fileName) + => CSharpCompilation.Create(assemblyName, syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, ParseOptionsLatestLangVer).WithFilePath(fileName) }, references: new[] { MetadataReference.CreateFromFile(typeof(Binder).Assembly.Location), From c5a4fee9e22b65c4bb769670cfab8b3beb57a4e2 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Fri, 5 Jul 2024 02:27:33 +0200 Subject: [PATCH 17/27] test is executed targeting docker db --- .../Dapper.AOT.Test.Integration.csproj | 11 ++-- .../DbStringTests.cs | 22 +++++--- .../InterceptionExecutables/DbString.cs | 24 +++++++-- .../IncludedTypes/Poco.cs | 7 +++ .../InterceptionExecutables/_Template.cs | 3 +- .../InterceptedCodeExecutionTestsBase.cs | 51 ++++++++++-------- test/Dapper.AOT.Test/GeneratorTestBase.cs | 3 +- .../TestCommon/RoslynTestHelpers.cs | 52 ++++++------------- 8 files changed, 101 insertions(+), 72 deletions(-) create mode 100644 test/Dapper.AOT.Test.Integration/InterceptionExecutables/IncludedTypes/Poco.cs diff --git a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj index d1013c01..89f817e5 100644 --- a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj +++ b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj @@ -9,9 +9,14 @@ - - - + + + + + + InterceptionExecutables/IncludedTypes/Poco.txt + PreserveNewest + diff --git a/test/Dapper.AOT.Test.Integration/DbStringTests.cs b/test/Dapper.AOT.Test.Integration/DbStringTests.cs index 829782e5..cfe3a99a 100644 --- a/test/Dapper.AOT.Test.Integration/DbStringTests.cs +++ b/test/Dapper.AOT.Test.Integration/DbStringTests.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Diagnostics; using System.Threading.Tasks; using Dapper.AOT.Test.Integration.Setup; using Xunit; @@ -12,11 +13,11 @@ public class DbStringTests : InterceptedCodeExecutionTestsBase public DbStringTests(PostgresqlFixture fixture, ITestOutputHelper log) : base(fixture, log) { Fixture.NpgsqlConnection.Execute(""" - CREATE TABLE IF NOT EXISTS dbStringTable( + CREATE TABLE IF NOT EXISTS dbStringTestsTable( id integer PRIMARY KEY, - name varchar(40) NOT NULL CHECK (name <> '') + name varchar(40) NOT NULL ); - TRUNCATE dbStringTable; + TRUNCATE dbStringTestsTable; """); } @@ -24,9 +25,18 @@ name varchar(40) NOT NULL CHECK (name <> '') [DapperAot] public async Task Test() { + await Fixture.NpgsqlConnection.ExecuteAsync(""" + INSERT INTO dbStringTestsTable(id, name) + VALUES (1, 'me testing!') + """); + var sourceCode = PrepareSourceCodeFromFile("DbString"); - var executionResults = BuildAndExecuteInterceptedUserCode(sourceCode, methodName: "ExecuteAsync"); - - // TODO DO THE CHECK HERE + var executionResults = BuildAndExecuteInterceptedUserCode(sourceCode, methodName: "Execute"); + + var list = Trace.Listeners; + Trace.Write("qwe"); + + Assert.NotNull(executionResults); + // TODO check that stack trace contains call to `Dapper.Aot.Generated.DbStringHelpers`. probably can be done like here https://stackoverflow.com/a/33939304 } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs index aae57a2f..76d009ff 100644 --- a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs +++ b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs @@ -8,13 +8,29 @@ namespace InterceptionExecutables using System.IO; using Dapper; using System.Threading.Tasks; - + using InterceptionExecutables.IncludedTypes; + + [DapperAot] // Enabling Dapper AOT! public static class Program { - public static async Task ExecuteAsync(IDbConnection dbConnection) + public static Poco Execute(IDbConnection dbConnection) + => ExecuteAsync(dbConnection).GetAwaiter().GetResult(); + + public static async Task ExecuteAsync(IDbConnection dbConnection) { - var res = await dbConnection.QueryAsync("SELECT count(*) FROM dbStringTable"); - return res.First(); + var results = await dbConnection.QueryAsync("select * from dbStringTestsTable where id = @Id and Name = @Name", new + { + Name = new DbString + { + Value = "me testing!", + IsFixedLength = false, + Length = 11 + }, + + Id = 1, + }); + + return results.First(); } } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/IncludedTypes/Poco.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/IncludedTypes/Poco.cs new file mode 100644 index 00000000..224b84e9 --- /dev/null +++ b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/IncludedTypes/Poco.cs @@ -0,0 +1,7 @@ +namespace InterceptionExecutables.IncludedTypes; + +public class Poco +{ + public int Id { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs index 9df40238..f0835087 100644 --- a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs +++ b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs @@ -4,8 +4,9 @@ using System.IO; using Dapper; using System.Threading.Tasks; + using InterceptionExecutables.IncludedTypes; - // this is just a sample for easy test-writing + [DapperAot] // Enabling Dapper AOT! public static class Program { public static async Task ExecuteAsync(IDbConnection dbConnection) diff --git a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs index 1b5408a3..9b60163a 100644 --- a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs +++ b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs @@ -6,7 +6,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; +using System.Text.Json; using Dapper.CodeAnalysis; using Dapper.TestCommon; using Microsoft.CodeAnalysis; @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Text; using Xunit; using Xunit.Abstractions; -using Xunit.Sdk; namespace Dapper.AOT.Test.Integration.Setup; @@ -41,23 +40,40 @@ protected static string PrepareSourceCodeFromFile(string inputFileName, string e } protected T BuildAndExecuteInterceptedUserCode( - string userSourceCode, + string sourceCode, string className = "Program", - string methodName = "ExecuteAsync") + string methodName = "Execute") { - var inputCompilation = RoslynTestHelpers.CreateCompilation("Assembly", syntaxTrees: [ - BuildInterceptorSupportedSyntaxTree(filename: "Program.cs", userSourceCode) - ]); + var syntaxTrees = new List + { + RoslynTestHelpers.CreateSyntaxTree(sourceCode, fileName: $"{className}.cs") + }; - var diagnosticsOutputStringBuilder = new StringBuilder(); - var (compilation, generatorDriverRunResult, diagnostics, errorCount) = Execute(inputCompilation, diagnosticsOutputStringBuilder, initializer: g => + var includedTypesPath = Path.Combine("InterceptionExecutables", "IncludedTypes"); + foreach (var file in Directory.GetFiles(includedTypesPath, "*.txt")) // we have copied all same files but in txt format not to have ambiguos references { - g.Log += message => Log(message); - }); + syntaxTrees.Add(RoslynTestHelpers.CreateSyntaxTree(File.ReadAllText(file), fileName: Path.GetFileName(file))); + } + + var inputCompilation = RoslynTestHelpers.CreateCompilation(assemblyName: "MyAssembly", syntaxTrees); + + var diagnosticsOutputStringBuilder = new StringBuilder(); + var (compilation, generatorDriverRunResult, diagnostics, errorCount) = Execute( + inputCompilation, + diagnosticsOutputStringBuilder, + initializer: g => { g.Log += message => Log(message); } + ); var results = Assert.Single(generatorDriverRunResult.Results); + Log($$""" + Generated code: + --- + {{results.GeneratedSources.First().SourceText}} + --- + """); + Assert.NotNull(compilation); - Assert.True(errorCount == 0, "User code should not report errors"); + Assert.True(errorCount == 0, $"Compilation errors: {diagnosticsOutputStringBuilder}"); var assembly = Compile(compilation!); var type = assembly.GetTypes().Single(t => t.FullName == $"InterceptionExecutables.{className}"); @@ -66,12 +82,9 @@ protected T BuildAndExecuteInterceptedUserCode( var result = mainMethod!.Invoke(obj: null, [ Fixture.NpgsqlConnection ]); Assert.NotNull(result); - if (result is not Task taskResult) - { - throw new XunitException($"expected execution result is '{typeof(Task)}' but got {result!.GetType()}"); - } - return taskResult.GetAwaiter().GetResult(); + var data = JsonSerializer.Serialize(result); + return JsonSerializer.Deserialize(data)!; } SyntaxTree BuildInterceptorSupportedSyntaxTree(string filename, string text) @@ -123,18 +136,14 @@ static void TryThrowErrors(IEnumerable items) class CompilationException : Exception { - public IEnumerable Errors { get; private set; } - public CompilationException(IEnumerable errors) : base(string.Join(Environment.NewLine, errors)) { - this.Errors = errors; } public CompilationException(params string[] errors) : base(string.Join(Environment.NewLine, errors)) { - this.Errors = errors; } } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/GeneratorTestBase.cs b/test/Dapper.AOT.Test/GeneratorTestBase.cs index acec3af9..d8095f64 100644 --- a/test/Dapper.AOT.Test/GeneratorTestBase.cs +++ b/test/Dapper.AOT.Test/GeneratorTestBase.cs @@ -32,7 +32,8 @@ protected GeneratorTestBase(ITestOutputHelper? log) // input from https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md#unit-testing-of-generators - protected (Compilation? Compilation, GeneratorDriverRunResult Result, ImmutableArray Diagnostics, int ErrorCount) Execute(string source, + protected (Compilation? Compilation, GeneratorDriverRunResult Result, ImmutableArray Diagnostics, int ErrorCount) Execute( + string source, StringBuilder? diagnosticsTo = null, [CallerMemberName] string? name = null, string? fileName = null, diff --git a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs index abb8d53a..cd76f1b3 100644 --- a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs +++ b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs @@ -12,6 +12,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; +using System.Text; using System.Threading.Tasks; namespace Dapper.TestCommon; @@ -43,44 +44,23 @@ public static class RoslynTestHelpers "RELEASE", #endif }) - .WithFeatures(new[] { DapperInterceptorGenerator.FeatureKeys.InterceptorsPreviewNamespacePair }); + .WithFeatures([ DapperInterceptorGenerator.FeatureKeys.InterceptorsPreviewNamespacePair ]); + + public static SyntaxTree CreateSyntaxTree(string source, string fileName) + => CSharpSyntaxTree.ParseText(source, ParseOptionsLatestLangVer, encoding: Encoding.UTF8).WithFilePath(fileName); - public static Compilation CreateCompilation(string assemblyName, SyntaxTree[] syntaxTrees, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) - => CSharpCompilation.Create(assemblyName, - syntaxTrees: syntaxTrees, - references: new[] { - MetadataReference.CreateFromFile(typeof(Binder).Assembly.Location), -#if !NET48 - MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), - MetadataReference.CreateFromFile(Assembly.Load("System.Data").Location), - MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location), - MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location), - MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.Schema.ColumnAttribute).Assembly.Location), -#endif - MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DbConnection).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Data.SqlClient.SqlConnection).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Microsoft.Data.SqlClient.SqlConnection).Assembly.Location), - MetadataReference.CreateFromFile(typeof(OracleConnection).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ValueTask).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Component).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DapperAotExtensions).Assembly.Location), - MetadataReference.CreateFromFile(typeof(SqlMapper).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ImmutableList).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), - MetadataReference.CreateFromFile(typeof(IAsyncEnumerable).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Span).Assembly.Location), - MetadataReference.CreateFromFile(typeof(IgnoreDataMemberAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(SqlMapper).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DynamicAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(IValidatableObject).Assembly.Location), - }, - options: new CSharpCompilationOptions(outputKind, allowUnsafe: true)); - public static Compilation CreateCompilation(string source, string assemblyName, string fileName) + { + var syntaxTree = CreateSyntaxTree(source, fileName); + return CreateCompilation(assemblyName, syntaxTree); + } + + public static Compilation CreateCompilation(string assemblyName, SyntaxTree syntaxTree) + => CreateCompilation(assemblyName, [syntaxTree]); + + public static Compilation CreateCompilation(string assemblyName, IEnumerable syntaxTrees) => CSharpCompilation.Create(assemblyName, - syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, ParseOptionsLatestLangVer).WithFilePath(fileName) }, + syntaxTrees: syntaxTrees, references: new[] { MetadataReference.CreateFromFile(typeof(Binder).Assembly.Location), #if !NET48 @@ -109,5 +89,5 @@ public static Compilation CreateCompilation(string source, string assemblyName, MetadataReference.CreateFromFile(typeof(DynamicAttribute).Assembly.Location), MetadataReference.CreateFromFile(typeof(IValidatableObject).Assembly.Location), }, - options: new CSharpCompilationOptions(OutputKind.ConsoleApplication, allowUnsafe: true)); + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); } \ No newline at end of file From 44e37d04db1c42b4aa59bf6224bb123798d62cff Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Fri, 5 Jul 2024 11:27:36 +0200 Subject: [PATCH 18/27] remove debug --- test/Dapper.AOT.Test.Integration/DbStringTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/Dapper.AOT.Test.Integration/DbStringTests.cs b/test/Dapper.AOT.Test.Integration/DbStringTests.cs index cfe3a99a..cc6e4a0f 100644 --- a/test/Dapper.AOT.Test.Integration/DbStringTests.cs +++ b/test/Dapper.AOT.Test.Integration/DbStringTests.cs @@ -32,9 +32,6 @@ INSERT INTO dbStringTestsTable(id, name) var sourceCode = PrepareSourceCodeFromFile("DbString"); var executionResults = BuildAndExecuteInterceptedUserCode(sourceCode, methodName: "Execute"); - - var list = Trace.Listeners; - Trace.Write("qwe"); Assert.NotNull(executionResults); // TODO check that stack trace contains call to `Dapper.Aot.Generated.DbStringHelpers`. probably can be done like here https://stackoverflow.com/a/33939304 From a95b6ff4e8b209b27a1a5d9c1f9bf4fa3be50ad1 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 21 Jul 2024 23:31:56 +0200 Subject: [PATCH 19/27] some other tries --- .../DapperInterceptorGenerator.cs | 2 ++ .../InGeneration/DapperHelpers.cs | 2 ++ .../Dapper.AOT.Test.Integration.csproj | 2 +- .../InterceptionExecutables/DbString.cs | 31 +++++++++++-------- .../InterceptedCodeExecutionTestsBase.cs | 31 +++++++++++++++++-- .../TestCommon/RoslynTestHelpers.cs | 6 +++- 6 files changed, 57 insertions(+), 17 deletions(-) diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs index 702feb08..49c8b371 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs @@ -312,6 +312,8 @@ internal void Generate(in GenerateState ctx) resultType = grp.First().ResultType!; sb.Append("// returns data: ").Append(resultType).NewLine(); } + + sb.Append($"throw new global::System.Exception(\"my test\");").NewLine(); // assertions var commandTypeMode = flags & (OperationFlags.Text | OperationFlags.StoredProcedure | OperationFlags.TableDirect); diff --git a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs index dbc9e5c0..b7fb5cec 100644 --- a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs +++ b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs @@ -12,6 +12,8 @@ public static void ConfigureDbStringDbParameter( global::System.Data.Common.DbParameter dbParameter, global::Dapper.DbString? dbString) { + throw new global::System.Exception("qwe"); + if (dbString is null) { dbParameter.Value = global::System.DBNull.Value; diff --git a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj index 89f817e5..b750aaee 100644 --- a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj +++ b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj @@ -1,7 +1,7 @@  - net6.0;net48 + net8.0;net48 Dapper.AOT.Test.Integration $(DefineConstants);DAPPERAOT_INTERNAL diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs index 76d009ff..8db744f3 100644 --- a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs +++ b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs @@ -18,19 +18,24 @@ public static Poco Execute(IDbConnection dbConnection) public static async Task ExecuteAsync(IDbConnection dbConnection) { - var results = await dbConnection.QueryAsync("select * from dbStringTestsTable where id = @Id and Name = @Name", new - { - Name = new DbString - { - Value = "me testing!", - IsFixedLength = false, - Length = 11 - }, - - Id = 1, - }); - - return results.First(); + var a = GetValue(); + return new Poco() { Id = 1, Name = a }; + + // var results = await dbConnection.QueryAsync("select * from dbStringTestsTable where id = @Id and Name = @Name", new + // { + // Name = new DbString + // { + // Value = "me testing!", + // IsFixedLength = false, + // Length = 11 + // }, + // + // Id = 1, + // }); + // + // return results.First(); } + + public static string GetValue() => "my-data"; } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs index 9b60163a..871d7b2c 100644 --- a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs +++ b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs @@ -68,13 +68,40 @@ protected T BuildAndExecuteInterceptedUserCode( Log($$""" Generated code: --- - {{results.GeneratedSources.First().SourceText}} + {{results.GeneratedSources.FirstOrDefault().SourceText}} --- """); + + compilation = compilation.AddSyntaxTrees(RoslynTestHelpers.CreateSyntaxTree(""" + namespace System.Runtime.CompilerServices + { + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) : Attribute + { + } + } + """, "attribute.cs")); + + compilation = compilation.AddSyntaxTrees(RoslynTestHelpers.CreateSyntaxTree($$""" + using System; + + namespace Dapper.AOT + { + public static class D + { + [System.Runtime.CompilerServices.InterceptsLocation(path: "Program.cs", lineNumber: 21, columnNumber: 21)] + internal static string GetValue() + { + return "changed-string"; + } + } + + } + """, "intercepted!")); Assert.NotNull(compilation); Assert.True(errorCount == 0, $"Compilation errors: {diagnosticsOutputStringBuilder}"); - + var assembly = Compile(compilation!); var type = assembly.GetTypes().Single(t => t.FullName == $"InterceptionExecutables.{className}"); var mainMethod = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); diff --git a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs index cd76f1b3..0fd73a3f 100644 --- a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs +++ b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs @@ -14,6 +14,7 @@ using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; +using Docker.DotNet.Models; namespace Dapper.TestCommon; @@ -44,7 +45,10 @@ public static class RoslynTestHelpers "RELEASE", #endif }) - .WithFeatures([ DapperInterceptorGenerator.FeatureKeys.InterceptorsPreviewNamespacePair ]); + .WithFeatures([ + new KeyValuePair(DapperInterceptorGenerator.FeatureKeys.InterceptorsPreviewNamespaces, $"{DapperInterceptorGenerator.FeatureKeys.CodegenNamespace};InterceptionExecutables"), + new KeyValuePair("LangVersion", "preview"), + ]); public static SyntaxTree CreateSyntaxTree(string source, string fileName) => CSharpSyntaxTree.ParseText(source, ParseOptionsLatestLangVer, encoding: Encoding.UTF8).WithFilePath(fileName); From 7203e05c0ae1631ce2e165caea56dd32e553c1de Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 21 Jul 2024 23:40:51 +0200 Subject: [PATCH 20/27] Revert "some other tries" This reverts commit a95b6ff4e8b209b27a1a5d9c1f9bf4fa3be50ad1. --- .../DapperInterceptorGenerator.cs | 2 -- .../InGeneration/DapperHelpers.cs | 2 -- .../Dapper.AOT.Test.Integration.csproj | 2 +- .../InterceptionExecutables/DbString.cs | 31 ++++++++----------- .../InterceptedCodeExecutionTestsBase.cs | 31 ++----------------- .../TestCommon/RoslynTestHelpers.cs | 6 +--- 6 files changed, 17 insertions(+), 57 deletions(-) diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs index 49c8b371..702feb08 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs @@ -312,8 +312,6 @@ internal void Generate(in GenerateState ctx) resultType = grp.First().ResultType!; sb.Append("// returns data: ").Append(resultType).NewLine(); } - - sb.Append($"throw new global::System.Exception(\"my test\");").NewLine(); // assertions var commandTypeMode = flags & (OperationFlags.Text | OperationFlags.StoredProcedure | OperationFlags.TableDirect); diff --git a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs index b7fb5cec..dbc9e5c0 100644 --- a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs +++ b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs @@ -12,8 +12,6 @@ public static void ConfigureDbStringDbParameter( global::System.Data.Common.DbParameter dbParameter, global::Dapper.DbString? dbString) { - throw new global::System.Exception("qwe"); - if (dbString is null) { dbParameter.Value = global::System.DBNull.Value; diff --git a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj index b750aaee..89f817e5 100644 --- a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj +++ b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj @@ -1,7 +1,7 @@  - net8.0;net48 + net6.0;net48 Dapper.AOT.Test.Integration $(DefineConstants);DAPPERAOT_INTERNAL diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs index 8db744f3..76d009ff 100644 --- a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs +++ b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs @@ -18,24 +18,19 @@ public static Poco Execute(IDbConnection dbConnection) public static async Task ExecuteAsync(IDbConnection dbConnection) { - var a = GetValue(); - return new Poco() { Id = 1, Name = a }; - - // var results = await dbConnection.QueryAsync("select * from dbStringTestsTable where id = @Id and Name = @Name", new - // { - // Name = new DbString - // { - // Value = "me testing!", - // IsFixedLength = false, - // Length = 11 - // }, - // - // Id = 1, - // }); - // - // return results.First(); - } + var results = await dbConnection.QueryAsync("select * from dbStringTestsTable where id = @Id and Name = @Name", new + { + Name = new DbString + { + Value = "me testing!", + IsFixedLength = false, + Length = 11 + }, + + Id = 1, + }); - public static string GetValue() => "my-data"; + return results.First(); + } } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs index 871d7b2c..9b60163a 100644 --- a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs +++ b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs @@ -68,40 +68,13 @@ protected T BuildAndExecuteInterceptedUserCode( Log($$""" Generated code: --- - {{results.GeneratedSources.FirstOrDefault().SourceText}} + {{results.GeneratedSources.First().SourceText}} --- """); - - compilation = compilation.AddSyntaxTrees(RoslynTestHelpers.CreateSyntaxTree(""" - namespace System.Runtime.CompilerServices - { - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) : Attribute - { - } - } - """, "attribute.cs")); - - compilation = compilation.AddSyntaxTrees(RoslynTestHelpers.CreateSyntaxTree($$""" - using System; - - namespace Dapper.AOT - { - public static class D - { - [System.Runtime.CompilerServices.InterceptsLocation(path: "Program.cs", lineNumber: 21, columnNumber: 21)] - internal static string GetValue() - { - return "changed-string"; - } - } - - } - """, "intercepted!")); Assert.NotNull(compilation); Assert.True(errorCount == 0, $"Compilation errors: {diagnosticsOutputStringBuilder}"); - + var assembly = Compile(compilation!); var type = assembly.GetTypes().Single(t => t.FullName == $"InterceptionExecutables.{className}"); var mainMethod = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); diff --git a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs index 0fd73a3f..cd76f1b3 100644 --- a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs +++ b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs @@ -14,7 +14,6 @@ using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; -using Docker.DotNet.Models; namespace Dapper.TestCommon; @@ -45,10 +44,7 @@ public static class RoslynTestHelpers "RELEASE", #endif }) - .WithFeatures([ - new KeyValuePair(DapperInterceptorGenerator.FeatureKeys.InterceptorsPreviewNamespaces, $"{DapperInterceptorGenerator.FeatureKeys.CodegenNamespace};InterceptionExecutables"), - new KeyValuePair("LangVersion", "preview"), - ]); + .WithFeatures([ DapperInterceptorGenerator.FeatureKeys.InterceptorsPreviewNamespacePair ]); public static SyntaxTree CreateSyntaxTree(string source, string fileName) => CSharpSyntaxTree.ParseText(source, ParseOptionsLatestLangVer, encoding: Encoding.UTF8).WithFilePath(fileName); From 423d92e1c1f03221091a8aa887218d478529890a Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 21 Jul 2024 23:40:52 +0200 Subject: [PATCH 21/27] Revert "remove debug" This reverts commit 44e37d04db1c42b4aa59bf6224bb123798d62cff. --- test/Dapper.AOT.Test.Integration/DbStringTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Dapper.AOT.Test.Integration/DbStringTests.cs b/test/Dapper.AOT.Test.Integration/DbStringTests.cs index cc6e4a0f..cfe3a99a 100644 --- a/test/Dapper.AOT.Test.Integration/DbStringTests.cs +++ b/test/Dapper.AOT.Test.Integration/DbStringTests.cs @@ -32,6 +32,9 @@ INSERT INTO dbStringTestsTable(id, name) var sourceCode = PrepareSourceCodeFromFile("DbString"); var executionResults = BuildAndExecuteInterceptedUserCode(sourceCode, methodName: "Execute"); + + var list = Trace.Listeners; + Trace.Write("qwe"); Assert.NotNull(executionResults); // TODO check that stack trace contains call to `Dapper.Aot.Generated.DbStringHelpers`. probably can be done like here https://stackoverflow.com/a/33939304 From 00b7025273ffb329b4eae3833d031aa7c4a6b73f Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 21 Jul 2024 23:40:52 +0200 Subject: [PATCH 22/27] Revert "test is executed targeting docker db" This reverts commit c5a4fee9e22b65c4bb769670cfab8b3beb57a4e2. --- .../Dapper.AOT.Test.Integration.csproj | 11 ++-- .../DbStringTests.cs | 22 +++----- .../InterceptionExecutables/DbString.cs | 24 ++------- .../IncludedTypes/Poco.cs | 7 --- .../InterceptionExecutables/_Template.cs | 3 +- .../InterceptedCodeExecutionTestsBase.cs | 51 ++++++++---------- test/Dapper.AOT.Test/GeneratorTestBase.cs | 3 +- .../TestCommon/RoslynTestHelpers.cs | 52 +++++++++++++------ 8 files changed, 72 insertions(+), 101 deletions(-) delete mode 100644 test/Dapper.AOT.Test.Integration/InterceptionExecutables/IncludedTypes/Poco.cs diff --git a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj index 89f817e5..d1013c01 100644 --- a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj +++ b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj @@ -9,14 +9,9 @@ - - - - - - InterceptionExecutables/IncludedTypes/Poco.txt - PreserveNewest - + + + diff --git a/test/Dapper.AOT.Test.Integration/DbStringTests.cs b/test/Dapper.AOT.Test.Integration/DbStringTests.cs index cfe3a99a..829782e5 100644 --- a/test/Dapper.AOT.Test.Integration/DbStringTests.cs +++ b/test/Dapper.AOT.Test.Integration/DbStringTests.cs @@ -1,5 +1,4 @@ using System.Data; -using System.Diagnostics; using System.Threading.Tasks; using Dapper.AOT.Test.Integration.Setup; using Xunit; @@ -13,11 +12,11 @@ public class DbStringTests : InterceptedCodeExecutionTestsBase public DbStringTests(PostgresqlFixture fixture, ITestOutputHelper log) : base(fixture, log) { Fixture.NpgsqlConnection.Execute(""" - CREATE TABLE IF NOT EXISTS dbStringTestsTable( + CREATE TABLE IF NOT EXISTS dbStringTable( id integer PRIMARY KEY, - name varchar(40) NOT NULL + name varchar(40) NOT NULL CHECK (name <> '') ); - TRUNCATE dbStringTestsTable; + TRUNCATE dbStringTable; """); } @@ -25,18 +24,9 @@ name varchar(40) NOT NULL [DapperAot] public async Task Test() { - await Fixture.NpgsqlConnection.ExecuteAsync(""" - INSERT INTO dbStringTestsTable(id, name) - VALUES (1, 'me testing!') - """); - var sourceCode = PrepareSourceCodeFromFile("DbString"); - var executionResults = BuildAndExecuteInterceptedUserCode(sourceCode, methodName: "Execute"); - - var list = Trace.Listeners; - Trace.Write("qwe"); - - Assert.NotNull(executionResults); - // TODO check that stack trace contains call to `Dapper.Aot.Generated.DbStringHelpers`. probably can be done like here https://stackoverflow.com/a/33939304 + var executionResults = BuildAndExecuteInterceptedUserCode(sourceCode, methodName: "ExecuteAsync"); + + // TODO DO THE CHECK HERE } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs index 76d009ff..aae57a2f 100644 --- a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs +++ b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs @@ -8,29 +8,13 @@ namespace InterceptionExecutables using System.IO; using Dapper; using System.Threading.Tasks; - using InterceptionExecutables.IncludedTypes; - - [DapperAot] // Enabling Dapper AOT! + public static class Program { - public static Poco Execute(IDbConnection dbConnection) - => ExecuteAsync(dbConnection).GetAwaiter().GetResult(); - - public static async Task ExecuteAsync(IDbConnection dbConnection) + public static async Task ExecuteAsync(IDbConnection dbConnection) { - var results = await dbConnection.QueryAsync("select * from dbStringTestsTable where id = @Id and Name = @Name", new - { - Name = new DbString - { - Value = "me testing!", - IsFixedLength = false, - Length = 11 - }, - - Id = 1, - }); - - return results.First(); + var res = await dbConnection.QueryAsync("SELECT count(*) FROM dbStringTable"); + return res.First(); } } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/IncludedTypes/Poco.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/IncludedTypes/Poco.cs deleted file mode 100644 index 224b84e9..00000000 --- a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/IncludedTypes/Poco.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace InterceptionExecutables.IncludedTypes; - -public class Poco -{ - public int Id { get; set; } - public string Name { get; set; } -} \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs index f0835087..9df40238 100644 --- a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs +++ b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs @@ -4,9 +4,8 @@ using System.IO; using Dapper; using System.Threading.Tasks; - using InterceptionExecutables.IncludedTypes; - [DapperAot] // Enabling Dapper AOT! + // this is just a sample for easy test-writing public static class Program { public static async Task ExecuteAsync(IDbConnection dbConnection) diff --git a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs index 9b60163a..1b5408a3 100644 --- a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs +++ b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs @@ -6,7 +6,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; -using System.Text.Json; +using System.Threading.Tasks; using Dapper.CodeAnalysis; using Dapper.TestCommon; using Microsoft.CodeAnalysis; @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Text; using Xunit; using Xunit.Abstractions; +using Xunit.Sdk; namespace Dapper.AOT.Test.Integration.Setup; @@ -40,40 +41,23 @@ protected static string PrepareSourceCodeFromFile(string inputFileName, string e } protected T BuildAndExecuteInterceptedUserCode( - string sourceCode, + string userSourceCode, string className = "Program", - string methodName = "Execute") + string methodName = "ExecuteAsync") { - var syntaxTrees = new List - { - RoslynTestHelpers.CreateSyntaxTree(sourceCode, fileName: $"{className}.cs") - }; - - var includedTypesPath = Path.Combine("InterceptionExecutables", "IncludedTypes"); - foreach (var file in Directory.GetFiles(includedTypesPath, "*.txt")) // we have copied all same files but in txt format not to have ambiguos references - { - syntaxTrees.Add(RoslynTestHelpers.CreateSyntaxTree(File.ReadAllText(file), fileName: Path.GetFileName(file))); - } - - var inputCompilation = RoslynTestHelpers.CreateCompilation(assemblyName: "MyAssembly", syntaxTrees); + var inputCompilation = RoslynTestHelpers.CreateCompilation("Assembly", syntaxTrees: [ + BuildInterceptorSupportedSyntaxTree(filename: "Program.cs", userSourceCode) + ]); var diagnosticsOutputStringBuilder = new StringBuilder(); - var (compilation, generatorDriverRunResult, diagnostics, errorCount) = Execute( - inputCompilation, - diagnosticsOutputStringBuilder, - initializer: g => { g.Log += message => Log(message); } - ); + var (compilation, generatorDriverRunResult, diagnostics, errorCount) = Execute(inputCompilation, diagnosticsOutputStringBuilder, initializer: g => + { + g.Log += message => Log(message); + }); var results = Assert.Single(generatorDriverRunResult.Results); - Log($$""" - Generated code: - --- - {{results.GeneratedSources.First().SourceText}} - --- - """); - Assert.NotNull(compilation); - Assert.True(errorCount == 0, $"Compilation errors: {diagnosticsOutputStringBuilder}"); + Assert.True(errorCount == 0, "User code should not report errors"); var assembly = Compile(compilation!); var type = assembly.GetTypes().Single(t => t.FullName == $"InterceptionExecutables.{className}"); @@ -82,9 +66,12 @@ protected T BuildAndExecuteInterceptedUserCode( var result = mainMethod!.Invoke(obj: null, [ Fixture.NpgsqlConnection ]); Assert.NotNull(result); + if (result is not Task taskResult) + { + throw new XunitException($"expected execution result is '{typeof(Task)}' but got {result!.GetType()}"); + } - var data = JsonSerializer.Serialize(result); - return JsonSerializer.Deserialize(data)!; + return taskResult.GetAwaiter().GetResult(); } SyntaxTree BuildInterceptorSupportedSyntaxTree(string filename, string text) @@ -136,14 +123,18 @@ static void TryThrowErrors(IEnumerable items) class CompilationException : Exception { + public IEnumerable Errors { get; private set; } + public CompilationException(IEnumerable errors) : base(string.Join(Environment.NewLine, errors)) { + this.Errors = errors; } public CompilationException(params string[] errors) : base(string.Join(Environment.NewLine, errors)) { + this.Errors = errors; } } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/GeneratorTestBase.cs b/test/Dapper.AOT.Test/GeneratorTestBase.cs index d8095f64..acec3af9 100644 --- a/test/Dapper.AOT.Test/GeneratorTestBase.cs +++ b/test/Dapper.AOT.Test/GeneratorTestBase.cs @@ -32,8 +32,7 @@ protected GeneratorTestBase(ITestOutputHelper? log) // input from https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md#unit-testing-of-generators - protected (Compilation? Compilation, GeneratorDriverRunResult Result, ImmutableArray Diagnostics, int ErrorCount) Execute( - string source, + protected (Compilation? Compilation, GeneratorDriverRunResult Result, ImmutableArray Diagnostics, int ErrorCount) Execute(string source, StringBuilder? diagnosticsTo = null, [CallerMemberName] string? name = null, string? fileName = null, diff --git a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs index cd76f1b3..abb8d53a 100644 --- a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs +++ b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs @@ -12,7 +12,6 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; -using System.Text; using System.Threading.Tasks; namespace Dapper.TestCommon; @@ -44,21 +43,9 @@ public static class RoslynTestHelpers "RELEASE", #endif }) - .WithFeatures([ DapperInterceptorGenerator.FeatureKeys.InterceptorsPreviewNamespacePair ]); + .WithFeatures(new[] { DapperInterceptorGenerator.FeatureKeys.InterceptorsPreviewNamespacePair }); - public static SyntaxTree CreateSyntaxTree(string source, string fileName) - => CSharpSyntaxTree.ParseText(source, ParseOptionsLatestLangVer, encoding: Encoding.UTF8).WithFilePath(fileName); - - public static Compilation CreateCompilation(string source, string assemblyName, string fileName) - { - var syntaxTree = CreateSyntaxTree(source, fileName); - return CreateCompilation(assemblyName, syntaxTree); - } - - public static Compilation CreateCompilation(string assemblyName, SyntaxTree syntaxTree) - => CreateCompilation(assemblyName, [syntaxTree]); - - public static Compilation CreateCompilation(string assemblyName, IEnumerable syntaxTrees) + public static Compilation CreateCompilation(string assemblyName, SyntaxTree[] syntaxTrees, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) => CSharpCompilation.Create(assemblyName, syntaxTrees: syntaxTrees, references: new[] { @@ -89,5 +76,38 @@ public static Compilation CreateCompilation(string assemblyName, IEnumerable CSharpCompilation.Create(assemblyName, + syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, ParseOptionsLatestLangVer).WithFilePath(fileName) }, + references: new[] { + MetadataReference.CreateFromFile(typeof(Binder).Assembly.Location), +#if !NET48 + MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), + MetadataReference.CreateFromFile(Assembly.Load("System.Data").Location), + MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location), + MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location), + MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.Schema.ColumnAttribute).Assembly.Location), +#endif + MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DbConnection).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.Data.SqlClient.SqlConnection).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Microsoft.Data.SqlClient.SqlConnection).Assembly.Location), + MetadataReference.CreateFromFile(typeof(OracleConnection).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ValueTask).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Component).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DapperAotExtensions).Assembly.Location), + MetadataReference.CreateFromFile(typeof(SqlMapper).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ImmutableList).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), + MetadataReference.CreateFromFile(typeof(IAsyncEnumerable).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Span).Assembly.Location), + MetadataReference.CreateFromFile(typeof(IgnoreDataMemberAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(SqlMapper).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DynamicAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(IValidatableObject).Assembly.Location), + }, + options: new CSharpCompilationOptions(OutputKind.ConsoleApplication, allowUnsafe: true)); } \ No newline at end of file From acff97e3d9f959587f37ee157feb9665c9e15000 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 21 Jul 2024 23:40:53 +0200 Subject: [PATCH 23/27] Revert "setup the infra for test" This reverts commit 63fd1a95bbe6672823ad19b959d187c499781bb7. --- .../InGeneration/DapperHelpers.cs | 2 +- .../Dapper.AOT.Test.Integration.csproj | 20 ++- .../DbStringTests.cs | 24 ++- .../InterceptionExecutables/DbString.cs | 20 --- .../InterceptionExecutables/_Template.cs | 18 --- .../InterceptedCodeExecutionTestsBase.cs | 140 ------------------ test/Dapper.AOT.Test/GeneratorTestBase.cs | 25 +--- .../TestCommon/RoslynTestHelpers.cs | 39 +---- 8 files changed, 32 insertions(+), 256 deletions(-) delete mode 100644 test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs delete mode 100644 test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs delete mode 100644 test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs diff --git a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs index dbc9e5c0..217d9da8 100644 --- a/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs +++ b/src/Dapper.AOT.Analyzers/InGeneration/DapperHelpers.cs @@ -6,7 +6,7 @@ #if !DAPPERAOT_INTERNAL file #endif - static partial class DbStringHelpers + static class DbStringHelpers { public static void ConfigureDbStringDbParameter( global::System.Data.Common.DbParameter dbParameter, diff --git a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj index d1013c01..755afdfa 100644 --- a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj +++ b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj @@ -7,17 +7,16 @@ - + + + + + + + - - - - - - - - - + + @@ -50,7 +49,6 @@ - diff --git a/test/Dapper.AOT.Test.Integration/DbStringTests.cs b/test/Dapper.AOT.Test.Integration/DbStringTests.cs index 829782e5..0e185aa0 100644 --- a/test/Dapper.AOT.Test.Integration/DbStringTests.cs +++ b/test/Dapper.AOT.Test.Integration/DbStringTests.cs @@ -1,32 +1,30 @@ -using System.Data; -using System.Threading.Tasks; +using System.Linq; using Dapper.AOT.Test.Integration.Setup; using Xunit; -using Xunit.Abstractions; namespace Dapper.AOT.Test.Integration; [Collection(SharedPostgresqlClient.Collection)] -public class DbStringTests : InterceptedCodeExecutionTestsBase +public class DbStringTests { - public DbStringTests(PostgresqlFixture fixture, ITestOutputHelper log) : base(fixture, log) + private PostgresqlFixture _fixture; + + public DbStringTests(PostgresqlFixture fixture) { - Fixture.NpgsqlConnection.Execute(""" + _fixture = fixture; + fixture.NpgsqlConnection.Execute(""" CREATE TABLE IF NOT EXISTS dbStringTable( id integer PRIMARY KEY, name varchar(40) NOT NULL CHECK (name <> '') ); TRUNCATE dbStringTable; - """); + """ + ); } [Fact] - [DapperAot] - public async Task Test() + public void ExecuteMulti() { - var sourceCode = PrepareSourceCodeFromFile("DbString"); - var executionResults = BuildAndExecuteInterceptedUserCode(sourceCode, methodName: "ExecuteAsync"); - - // TODO DO THE CHECK HERE + } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs deleted file mode 100644 index aae57a2f..00000000 --- a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/DbString.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Data; -using System.Data.SqlClient; -using System.Linq; - -namespace InterceptionExecutables -{ - using System; - using System.IO; - using Dapper; - using System.Threading.Tasks; - - public static class Program - { - public static async Task ExecuteAsync(IDbConnection dbConnection) - { - var res = await dbConnection.QueryAsync("SELECT count(*) FROM dbStringTable"); - return res.First(); - } - } -} \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs b/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs deleted file mode 100644 index 9df40238..00000000 --- a/test/Dapper.AOT.Test.Integration/InterceptionExecutables/_Template.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace InterceptionExecutables -{ - using System; - using System.IO; - using Dapper; - using System.Threading.Tasks; - - // this is just a sample for easy test-writing - public static class Program - { - public static async Task ExecuteAsync(IDbConnection dbConnection) - { - - } - } - - -} \ No newline at end of file diff --git a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs b/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs deleted file mode 100644 index 1b5408a3..00000000 --- a/test/Dapper.AOT.Test.Integration/Setup/InterceptedCodeExecutionTestsBase.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Dapper.CodeAnalysis; -using Dapper.TestCommon; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.Text; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Dapper.AOT.Test.Integration.Setup; - -public abstract class InterceptedCodeExecutionTestsBase : GeneratorTestBase -{ - protected readonly PostgresqlFixture Fixture; - - protected InterceptedCodeExecutionTestsBase(PostgresqlFixture fixture, ITestOutputHelper? log) : base(log) - { - Fixture = fixture; - } - - protected static string PrepareSourceCodeFromFile(string inputFileName, string extension = ".cs") - { - var fullPath = Path.Combine("InterceptionExecutables", inputFileName + extension); - if (!File.Exists(fullPath)) - { - throw new FileNotFoundException(fullPath); - } - - using var sr = new StreamReader(fullPath); - return sr.ReadToEnd(); - } - - protected T BuildAndExecuteInterceptedUserCode( - string userSourceCode, - string className = "Program", - string methodName = "ExecuteAsync") - { - var inputCompilation = RoslynTestHelpers.CreateCompilation("Assembly", syntaxTrees: [ - BuildInterceptorSupportedSyntaxTree(filename: "Program.cs", userSourceCode) - ]); - - var diagnosticsOutputStringBuilder = new StringBuilder(); - var (compilation, generatorDriverRunResult, diagnostics, errorCount) = Execute(inputCompilation, diagnosticsOutputStringBuilder, initializer: g => - { - g.Log += message => Log(message); - }); - - var results = Assert.Single(generatorDriverRunResult.Results); - Assert.NotNull(compilation); - Assert.True(errorCount == 0, "User code should not report errors"); - - var assembly = Compile(compilation!); - var type = assembly.GetTypes().Single(t => t.FullName == $"InterceptionExecutables.{className}"); - var mainMethod = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); - Assert.NotNull(mainMethod); - - var result = mainMethod!.Invoke(obj: null, [ Fixture.NpgsqlConnection ]); - Assert.NotNull(result); - if (result is not Task taskResult) - { - throw new XunitException($"expected execution result is '{typeof(Task)}' but got {result!.GetType()}"); - } - - return taskResult.GetAwaiter().GetResult(); - } - - SyntaxTree BuildInterceptorSupportedSyntaxTree(string filename, string text) - { - var options = new CSharpParseOptions(LanguageVersion.Preview) - .WithFeatures(new [] - { - new KeyValuePair("InterceptorsPreviewNamespaces", "$(InterceptorsPreviewNamespaces);ProgramNamespace;Dapper.AOT"), - new KeyValuePair("Features", "InterceptorsPreview"), - new KeyValuePair("LangVersion", "preview"), - }); - - var stringText = SourceText.From(text, Encoding.UTF8); - return SyntaxFactory.ParseSyntaxTree(stringText, options, filename); - } - - static Assembly Compile(Compilation compilation) - { - using var peStream = new MemoryStream(); - using var pdbstream = new MemoryStream(); - - var dbg = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? DebugInformationFormat.Pdb : DebugInformationFormat.PortablePdb; - var emitResult = compilation.Emit(peStream, pdbstream, null, null, null, new EmitOptions(false, dbg)); - if (!emitResult.Success) - { - TryThrowErrors(emitResult.Diagnostics); - } - - peStream.Position = pdbstream.Position = 0; - return Assembly.Load(peStream.ToArray(), pdbstream.ToArray()); - } - - static void TryThrowErrors(IEnumerable items) - { - var errors = new List(); - foreach (var item in items) - { - if (item.Severity == DiagnosticSeverity.Error) - { - errors.Add(item.GetMessage(CultureInfo.InvariantCulture)); - } - } - - if (errors.Count > 0) - { - throw new CompilationException(errors); - } - } - - class CompilationException : Exception - { - public IEnumerable Errors { get; private set; } - - public CompilationException(IEnumerable errors) - : base(string.Join(Environment.NewLine, errors)) - { - this.Errors = errors; - } - - public CompilationException(params string[] errors) - : base(string.Join(Environment.NewLine, errors)) - { - this.Errors = errors; - } - } -} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/GeneratorTestBase.cs b/test/Dapper.AOT.Test/GeneratorTestBase.cs index acec3af9..bf5d86f1 100644 --- a/test/Dapper.AOT.Test/GeneratorTestBase.cs +++ b/test/Dapper.AOT.Test/GeneratorTestBase.cs @@ -31,27 +31,13 @@ protected GeneratorTestBase(ITestOutputHelper? log) protected static string? GetOriginCodeLocation([CallerFilePath] string? path = null) => path; // input from https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md#unit-testing-of-generators - + protected (Compilation? Compilation, GeneratorDriverRunResult Result, ImmutableArray Diagnostics, int ErrorCount) Execute(string source, StringBuilder? diagnosticsTo = null, [CallerMemberName] string? name = null, string? fileName = null, Action? initializer = null ) where T : class, IIncrementalGenerator, new() - { - // Create the 'input' compilation that the generator will act on - if (string.IsNullOrWhiteSpace(name)) name = "compilation"; - if (string.IsNullOrWhiteSpace(fileName)) fileName = "input.cs"; - var inputCompilation = RoslynTestHelpers.CreateCompilation(source, name!, fileName!); - - return Execute(inputCompilation, diagnosticsTo, initializer); - } - - protected (Compilation? Compilation, GeneratorDriverRunResult Result, ImmutableArray Diagnostics, int ErrorCount) Execute( - Compilation inputCompilation, - StringBuilder? diagnosticsTo = null, - Action? initializer = null - ) where T : class, IIncrementalGenerator, new() { void OutputDiagnostic(Diagnostic d) { @@ -68,13 +54,18 @@ void Output(string message, bool force = false) diagnosticsTo?.AppendLine(message.Replace('\\', '/')); // need to normalize paths } } + // Create the 'input' compilation that the generator will act on + if (string.IsNullOrWhiteSpace(name)) name = "compilation"; + if (string.IsNullOrWhiteSpace(fileName)) fileName = "input.cs"; + Compilation inputCompilation = RoslynTestHelpers.CreateCompilation(source, name!, fileName!); + // directly create an instance of the generator // (Note: in the compiler this is loaded from an assembly, and created via reflection at runtime) T generator = new(); initializer?.Invoke(generator); ShowDiagnostics("Input code", inputCompilation, diagnosticsTo, "CS8795", "CS1701", "CS1702"); - + // Create the driver that will control the generation, passing in our generator GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { generator.AsSourceGenerator() }, parseOptions: RoslynTestHelpers.ParseOptionsLatestLangVer); @@ -82,7 +73,7 @@ void Output(string message, bool force = false) // (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls) driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); var runResult = driver.GetRunResult(); - + foreach (var result in runResult.Results) { if (result.Exception is not null) throw result.Exception; diff --git a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs index abb8d53a..02172345 100644 --- a/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs +++ b/test/Dapper.AOT.Test/TestCommon/RoslynTestHelpers.cs @@ -16,7 +16,7 @@ namespace Dapper.TestCommon; -public static class RoslynTestHelpers +internal static class RoslynTestHelpers { internal static readonly CSharpParseOptions ParseOptionsLatestLangVer = CSharpParseOptions.Default .WithLanguageVersion(LanguageVersion.Latest) @@ -45,41 +45,8 @@ public static class RoslynTestHelpers }) .WithFeatures(new[] { DapperInterceptorGenerator.FeatureKeys.InterceptorsPreviewNamespacePair }); - public static Compilation CreateCompilation(string assemblyName, SyntaxTree[] syntaxTrees, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) - => CSharpCompilation.Create(assemblyName, - syntaxTrees: syntaxTrees, - references: new[] { - MetadataReference.CreateFromFile(typeof(Binder).Assembly.Location), -#if !NET48 - MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), - MetadataReference.CreateFromFile(Assembly.Load("System.Data").Location), - MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location), - MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location), - MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.Schema.ColumnAttribute).Assembly.Location), -#endif - MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DbConnection).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Data.SqlClient.SqlConnection).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Microsoft.Data.SqlClient.SqlConnection).Assembly.Location), - MetadataReference.CreateFromFile(typeof(OracleConnection).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ValueTask).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Component).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DapperAotExtensions).Assembly.Location), - MetadataReference.CreateFromFile(typeof(SqlMapper).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ImmutableList).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), - MetadataReference.CreateFromFile(typeof(IAsyncEnumerable).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Span).Assembly.Location), - MetadataReference.CreateFromFile(typeof(IgnoreDataMemberAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(SqlMapper).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DynamicAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(IValidatableObject).Assembly.Location), - }, - options: new CSharpCompilationOptions(outputKind, allowUnsafe: true)); - - public static Compilation CreateCompilation(string source, string assemblyName, string fileName) - => CSharpCompilation.Create(assemblyName, + public static Compilation CreateCompilation(string source, string name, string fileName) + => CSharpCompilation.Create(name, syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, ParseOptionsLatestLangVer).WithFilePath(fileName) }, references: new[] { MetadataReference.CreateFromFile(typeof(Binder).Assembly.Location), From 835655b6559b511967bb99ff0c5d38f074017ce9 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Sun, 21 Jul 2024 23:40:54 +0200 Subject: [PATCH 24/27] Revert "move to Dapper.AOT.Test.Integration.csproj" This reverts commit e80464847eb93880d6b4c36227a32897281b32f8. --- Dapper.AOT.sln | 7 --- .../Dapper.AOT.Test.Integration.csproj | 54 ------------------- .../DbStringTests.cs | 30 ----------- test/Dapper.AOT.Test/Dapper.AOT.Test.csproj | 2 +- .../Helpers/XUnitSkippable.cs | 0 .../Integration}/BatchPostgresql.cs | 1 - .../Integration}/BatchTests.cs | 1 - .../BulkInsertIngegrationTests.cs | 1 - .../Integration}/DynamicTests.cs | 1 - .../Integration}/ManualGridReaderTests.cs | 6 +-- .../Integration}/PostgresqlFixture.cs | 6 +-- .../Integration}/QueryTests.cs | 1 - .../Integration}/SqlClientFixture.cs | 6 +-- .../Interceptors/DbString.output.netfx.cs | 3 -- 14 files changed, 10 insertions(+), 109 deletions(-) delete mode 100644 test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj delete mode 100644 test/Dapper.AOT.Test.Integration/DbStringTests.cs rename test/{Dapper.AOT.Test.Integration => Dapper.AOT.Test}/Helpers/XUnitSkippable.cs (100%) rename test/{Dapper.AOT.Test.Integration => Dapper.AOT.Test/Integration}/BatchPostgresql.cs (98%) rename test/{Dapper.AOT.Test.Integration => Dapper.AOT.Test/Integration}/BatchTests.cs (99%) rename test/{Dapper.AOT.Test.Integration => Dapper.AOT.Test/Integration}/BulkInsertIngegrationTests.cs (99%) rename test/{Dapper.AOT.Test.Integration => Dapper.AOT.Test/Integration}/DynamicTests.cs (95%) rename test/{Dapper.AOT.Test.Integration => Dapper.AOT.Test/Integration}/ManualGridReaderTests.cs (98%) rename test/{Dapper.AOT.Test.Integration/Setup => Dapper.AOT.Test/Integration}/PostgresqlFixture.cs (93%) rename test/{Dapper.AOT.Test.Integration => Dapper.AOT.Test/Integration}/QueryTests.cs (99%) rename test/{Dapper.AOT.Test.Integration/Setup => Dapper.AOT.Test/Integration}/SqlClientFixture.cs (96%) diff --git a/Dapper.AOT.sln b/Dapper.AOT.sln index 17d61ea5..15a5e283 100644 --- a/Dapper.AOT.sln +++ b/Dapper.AOT.sln @@ -40,8 +40,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UsageVanilla", "test\UsageVanilla\UsageVanilla.csproj", "{840EA1CA-62FF-409E-89F5-CD3BB269BAE3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.AOT.Test.Integration", "test\Dapper.AOT.Test.Integration\Dapper.AOT.Test.Integration.csproj", "{63423B18-04F1-4571-ABB2-8D0925B0C3EB}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -80,10 +78,6 @@ Global {840EA1CA-62FF-409E-89F5-CD3BB269BAE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {840EA1CA-62FF-409E-89F5-CD3BB269BAE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {840EA1CA-62FF-409E-89F5-CD3BB269BAE3}.Release|Any CPU.Build.0 = Release|Any CPU - {63423B18-04F1-4571-ABB2-8D0925B0C3EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63423B18-04F1-4571-ABB2-8D0925B0C3EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63423B18-04F1-4571-ABB2-8D0925B0C3EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63423B18-04F1-4571-ABB2-8D0925B0C3EB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -97,7 +91,6 @@ Global {A77B633C-573E-43CD-85A4-8063B33143B4} = {FE215D4B-811B-47BB-9F05-6382DD1C6729} {C6527566-38F4-43CC-9E0E-91C4B8854774} = {1135D4FD-770E-41DF-920B-A8F75E42A832} {840EA1CA-62FF-409E-89F5-CD3BB269BAE3} = {9A846B95-90CE-4335-9043-48C5B8EA4FB8} - {63423B18-04F1-4571-ABB2-8D0925B0C3EB} = {9A846B95-90CE-4335-9043-48C5B8EA4FB8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A89CDAFA-494F-4168-9648-1138BA738D43} diff --git a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj b/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj deleted file mode 100644 index 755afdfa..00000000 --- a/test/Dapper.AOT.Test.Integration/Dapper.AOT.Test.Integration.csproj +++ /dev/null @@ -1,54 +0,0 @@ - - - - net6.0;net48 - Dapper.AOT.Test.Integration - $(DefineConstants);DAPPERAOT_INTERNAL - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - - - - - - - - diff --git a/test/Dapper.AOT.Test.Integration/DbStringTests.cs b/test/Dapper.AOT.Test.Integration/DbStringTests.cs deleted file mode 100644 index 0e185aa0..00000000 --- a/test/Dapper.AOT.Test.Integration/DbStringTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using Dapper.AOT.Test.Integration.Setup; -using Xunit; - -namespace Dapper.AOT.Test.Integration; - -[Collection(SharedPostgresqlClient.Collection)] -public class DbStringTests -{ - private PostgresqlFixture _fixture; - - public DbStringTests(PostgresqlFixture fixture) - { - _fixture = fixture; - fixture.NpgsqlConnection.Execute(""" - CREATE TABLE IF NOT EXISTS dbStringTable( - id integer PRIMARY KEY, - name varchar(40) NOT NULL CHECK (name <> '') - ); - TRUNCATE dbStringTable; - """ - ); - } - - [Fact] - public void ExecuteMulti() - { - - } -} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj index c96c32a3..67a758e5 100644 --- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj +++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj @@ -26,8 +26,8 @@ $([System.String]::Copy(%(Filename)).Replace('.VB.cs', '.cs')) + - diff --git a/test/Dapper.AOT.Test.Integration/Helpers/XUnitSkippable.cs b/test/Dapper.AOT.Test/Helpers/XUnitSkippable.cs similarity index 100% rename from test/Dapper.AOT.Test.Integration/Helpers/XUnitSkippable.cs rename to test/Dapper.AOT.Test/Helpers/XUnitSkippable.cs diff --git a/test/Dapper.AOT.Test.Integration/BatchPostgresql.cs b/test/Dapper.AOT.Test/Integration/BatchPostgresql.cs similarity index 98% rename from test/Dapper.AOT.Test.Integration/BatchPostgresql.cs rename to test/Dapper.AOT.Test/Integration/BatchPostgresql.cs index 274ca64b..55fb266c 100644 --- a/test/Dapper.AOT.Test.Integration/BatchPostgresql.cs +++ b/test/Dapper.AOT.Test/Integration/BatchPostgresql.cs @@ -1,6 +1,5 @@ using System.Data; using System.Linq; -using Dapper.AOT.Test.Integration.Setup; using Xunit; namespace Dapper.AOT.Test.Integration; diff --git a/test/Dapper.AOT.Test.Integration/BatchTests.cs b/test/Dapper.AOT.Test/Integration/BatchTests.cs similarity index 99% rename from test/Dapper.AOT.Test.Integration/BatchTests.cs rename to test/Dapper.AOT.Test/Integration/BatchTests.cs index 8ca88475..d154bdce 100644 --- a/test/Dapper.AOT.Test.Integration/BatchTests.cs +++ b/test/Dapper.AOT.Test/Integration/BatchTests.cs @@ -5,7 +5,6 @@ using System.Collections.ObjectModel; using System.Data; using System.Linq; -using Dapper.AOT.Test.Integration.Setup; using Xunit; namespace Dapper.AOT.Test.Integration; diff --git a/test/Dapper.AOT.Test.Integration/BulkInsertIngegrationTests.cs b/test/Dapper.AOT.Test/Integration/BulkInsertIngegrationTests.cs similarity index 99% rename from test/Dapper.AOT.Test.Integration/BulkInsertIngegrationTests.cs rename to test/Dapper.AOT.Test/Integration/BulkInsertIngegrationTests.cs index 706c724f..4d827379 100644 --- a/test/Dapper.AOT.Test.Integration/BulkInsertIngegrationTests.cs +++ b/test/Dapper.AOT.Test/Integration/BulkInsertIngegrationTests.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Dapper.AOT.Test.Integration.Setup; using Xunit; namespace Dapper.AOT.Test.Integration; diff --git a/test/Dapper.AOT.Test.Integration/DynamicTests.cs b/test/Dapper.AOT.Test/Integration/DynamicTests.cs similarity index 95% rename from test/Dapper.AOT.Test.Integration/DynamicTests.cs rename to test/Dapper.AOT.Test/Integration/DynamicTests.cs index 7e18b7fa..46553881 100644 --- a/test/Dapper.AOT.Test.Integration/DynamicTests.cs +++ b/test/Dapper.AOT.Test/Integration/DynamicTests.cs @@ -1,7 +1,6 @@ using Microsoft.Data.SqlClient; using System; using System.Collections.Generic; -using Dapper.AOT.Test.Integration.Setup; using Xunit; namespace Dapper.AOT.Test.Integration; diff --git a/test/Dapper.AOT.Test.Integration/ManualGridReaderTests.cs b/test/Dapper.AOT.Test/Integration/ManualGridReaderTests.cs similarity index 98% rename from test/Dapper.AOT.Test.Integration/ManualGridReaderTests.cs rename to test/Dapper.AOT.Test/Integration/ManualGridReaderTests.cs index c4861da4..eade42b9 100644 --- a/test/Dapper.AOT.Test.Integration/ManualGridReaderTests.cs +++ b/test/Dapper.AOT.Test/Integration/ManualGridReaderTests.cs @@ -1,10 +1,10 @@ -using System; +using Microsoft.Data.SqlClient; +using System; using System.Linq; using System.Threading.Tasks; -using Microsoft.Data.SqlClient; using Xunit; -namespace Dapper.AOT.Test.Integration.Setup; +namespace Dapper.AOT.Test.Integration; [Collection(SharedSqlClient.Collection)] public class ManualGridReaderTests : IDisposable diff --git a/test/Dapper.AOT.Test.Integration/Setup/PostgresqlFixture.cs b/test/Dapper.AOT.Test/Integration/PostgresqlFixture.cs similarity index 93% rename from test/Dapper.AOT.Test.Integration/Setup/PostgresqlFixture.cs rename to test/Dapper.AOT.Test/Integration/PostgresqlFixture.cs index 1023b18b..59329192 100644 --- a/test/Dapper.AOT.Test.Integration/Setup/PostgresqlFixture.cs +++ b/test/Dapper.AOT.Test/Integration/PostgresqlFixture.cs @@ -1,9 +1,9 @@ -using System.Threading.Tasks; -using Npgsql; +using Npgsql; +using System.Threading.Tasks; using Testcontainers.PostgreSql; using Xunit; -namespace Dapper.AOT.Test.Integration.Setup; +namespace Dapper.AOT.Test.Integration; [CollectionDefinition(Collection)] public class SharedPostgresqlClient : ICollectionFixture diff --git a/test/Dapper.AOT.Test.Integration/QueryTests.cs b/test/Dapper.AOT.Test/Integration/QueryTests.cs similarity index 99% rename from test/Dapper.AOT.Test.Integration/QueryTests.cs rename to test/Dapper.AOT.Test/Integration/QueryTests.cs index 3f660a23..7241759c 100644 --- a/test/Dapper.AOT.Test.Integration/QueryTests.cs +++ b/test/Dapper.AOT.Test/Integration/QueryTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Data.Common; using System.Threading.Tasks; -using Dapper.AOT.Test.Integration.Setup; using Xunit; namespace Dapper.AOT.Test.Integration; diff --git a/test/Dapper.AOT.Test.Integration/Setup/SqlClientFixture.cs b/test/Dapper.AOT.Test/Integration/SqlClientFixture.cs similarity index 96% rename from test/Dapper.AOT.Test.Integration/Setup/SqlClientFixture.cs rename to test/Dapper.AOT.Test/Integration/SqlClientFixture.cs index da22c4b8..860d85dc 100644 --- a/test/Dapper.AOT.Test.Integration/Setup/SqlClientFixture.cs +++ b/test/Dapper.AOT.Test/Integration/SqlClientFixture.cs @@ -1,8 +1,8 @@ -using System; -using Microsoft.Data.SqlClient; +using Microsoft.Data.SqlClient; +using System; using Xunit; -namespace Dapper.AOT.Test.Integration.Setup; +namespace Dapper.AOT.Test.Integration; [CollectionDefinition(Collection)] public class SharedSqlClient : ICollectionFixture diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs index fa609c25..98e93105 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs @@ -143,9 +143,6 @@ namespace Dapper.Aot.Generated /// /// Contains helpers to properly handle /// -#if !DAPPERAOT_INTERNAL - file -#endif static class DbStringHelpers { public static void ConfigureDbStringDbParameter( From 92523ae0428562499de5e21f53a2c105d7e0e76d Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Thu, 25 Jul 2024 23:51:46 +0200 Subject: [PATCH 25/27] address PR comments --- docs/rules/DAP048.md | 2 +- src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/rules/DAP048.md b/docs/rules/DAP048.md index 3dfec4ad..6bf5fca2 100644 --- a/docs/rules/DAP048.md +++ b/docs/rules/DAP048.md @@ -1,6 +1,6 @@ # DAP048 -[DbString](https://github.com/DapperLib/Dapper/blob/main/Dapper/DbString.cs) is allocatey type, but achieves the same as +[DbString](https://github.com/DapperLib/Dapper/blob/main/Dapper/DbString.cs) causes heap allocations, but achieves the same as [DbValueAttribute](https://github.com/DapperLib/DapperAOT/blob/main/src/Dapper.AOT/DbValueAttribute.cs). Bad: diff --git a/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj b/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj index f74b74e8..47f8010a 100644 --- a/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj +++ b/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj @@ -16,11 +16,10 @@ - - - - + + + DapperInterceptorGenerator.cs From 64ae716cef93cc9091e56d6f42bdaadec576197e Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Fri, 26 Jul 2024 00:49:23 +0200 Subject: [PATCH 26/27] adjust a test --- .../Interceptors/DbString.input.cs | 13 ++- .../Interceptors/DbString.output.cs | 90 +++++++++++++++--- .../Interceptors/DbString.output.netfx.cs | 93 ++++++++++++++++--- .../Interceptors/DbString.output.netfx.txt | 2 +- .../Interceptors/DbString.output.txt | 2 +- 5 files changed, 171 insertions(+), 29 deletions(-) diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.input.cs b/test/Dapper.AOT.Test/Interceptors/DbString.input.cs index 633ce3e0..b646a479 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.input.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.input.cs @@ -8,7 +8,7 @@ public static class Foo { static async Task SomeCode(DbConnection connection) { - _ = await connection.QueryAsync("select * from Orders where name = @Name and id = @Id", new + _ = await connection.QueryAsync("select * from Orders where name = @Name and id = @Id", new { Name = new DbString { @@ -21,7 +21,7 @@ static async Task SomeCode(DbConnection connection) Id = 123 }); - _ = await connection.QueryAsync("select * from Orders where name = @Name and id = @Id", new Poco + _ = await connection.QueryAsync("select * from Orders where name = @Name and id = @Id", new QueryModel { Name = new DbString { @@ -35,9 +35,16 @@ static async Task SomeCode(DbConnection connection) }); } - public class Poco + public class QueryModel { public DbString Name { get; set; } public int Id { get; set; } } + + public class Product + { + public int ProductId { get; set; } + public string Name { get; set; } + public string ProductNumber { get; set; } + } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs index fa609c25..61d5b059 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs @@ -4,34 +4,34 @@ namespace Dapper.AOT // interceptors must be in a known namespace file static class DapperGeneratedInterceptors { [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DbString.input.cs", 11, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + internal static global::System.Threading.Tasks.Task> QueryAsync0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { - // Query, Async, TypedResult, HasParameters, Buffered, Text, KnownParameters + // Query, Async, TypedResult, HasParameters, Buffered, Text, BindResultsByName, KnownParameters // takes parameter: // parameter map: Id Name - // returns data: int + // returns data: global::Foo.Product global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); global::System.Diagnostics.Debug.Assert(param is not null); return global::Dapper.DapperAotExtensions.AsEnumerableAsync( - global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory0.Instance).QueryBufferedAsync(param, global::Dapper.RowFactory.Inbuilt.Value())); + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory0.Instance).QueryBufferedAsync(param, RowFactory0.Instance)); } [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DbString.input.cs", 24, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + internal static global::System.Threading.Tasks.Task> QueryAsync1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { - // Query, Async, TypedResult, HasParameters, Buffered, Text, KnownParameters - // takes parameter: global::Foo.Poco + // Query, Async, TypedResult, HasParameters, Buffered, Text, BindResultsByName, KnownParameters + // takes parameter: global::Foo.QueryModel // parameter map: Id Name - // returns data: int + // returns data: global::Foo.Product global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); global::System.Diagnostics.Debug.Assert(param is not null); return global::Dapper.DapperAotExtensions.AsEnumerableAsync( - global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory1.Instance).QueryBufferedAsync((global::Foo.Poco)param!, global::Dapper.RowFactory.Inbuilt.Value())); + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory1.Instance).QueryBufferedAsync((global::Foo.QueryModel)param!, RowFactory0.Instance)); } @@ -54,6 +54,72 @@ private class CommonCommandFactory : global::Dapper.CommandFactory private static readonly CommonCommandFactory DefaultCommandFactory = new(); + private sealed class RowFactory0 : global::Dapper.RowFactory + { + internal static readonly RowFactory0 Instance = new(); + private RowFactory0() {} + public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) + { + for (int i = 0; i < tokens.Length; i++) + { + int token = -1; + var name = reader.GetName(columnOffset); + var type = reader.GetFieldType(columnOffset); + switch (NormalizedHash(name)) + { + case 2521315361U when NormalizedEquals(name, "productid"): + token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible + break; + case 2369371622U when NormalizedEquals(name, "name"): + token = type == typeof(string) ? 1 : 4; + break; + case 1133313085U when NormalizedEquals(name, "productnumber"): + token = type == typeof(string) ? 2 : 5; + break; + + } + tokens[i] = token; + columnOffset++; + + } + return null; + } + public override global::Foo.Product Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) + { + global::Foo.Product result = new(); + foreach (var token in tokens) + { + switch (token) + { + case 0: + result.ProductId = reader.GetInt32(columnOffset); + break; + case 3: + result.ProductId = GetValue(reader, columnOffset); + break; + case 1: + result.Name = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); + break; + case 4: + result.Name = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); + break; + case 2: + result.ProductNumber = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); + break; + case 5: + result.ProductNumber = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); + break; + + } + columnOffset++; + + } + return result; + + } + + } + private sealed class CommandFactory0 : CommonCommandFactory // { internal static readonly CommandFactory0 Instance = new(); @@ -87,10 +153,10 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje } - private sealed class CommandFactory1 : CommonCommandFactory + private sealed class CommandFactory1 : CommonCommandFactory { internal static readonly CommandFactory1 Instance = new(); - public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.Poco args) + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.QueryModel args) { var ps = cmd.Parameters; global::System.Data.Common.DbParameter p; @@ -107,7 +173,7 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global: ps.Add(p); } - public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.Poco args) + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.QueryModel args) { var ps = cmd.Parameters; global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(ps[0], args.Name); diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs index 98e93105..61d5b059 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs @@ -4,34 +4,34 @@ namespace Dapper.AOT // interceptors must be in a known namespace file static class DapperGeneratedInterceptors { [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DbString.input.cs", 11, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + internal static global::System.Threading.Tasks.Task> QueryAsync0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { - // Query, Async, TypedResult, HasParameters, Buffered, Text, KnownParameters + // Query, Async, TypedResult, HasParameters, Buffered, Text, BindResultsByName, KnownParameters // takes parameter: // parameter map: Id Name - // returns data: int + // returns data: global::Foo.Product global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); global::System.Diagnostics.Debug.Assert(param is not null); return global::Dapper.DapperAotExtensions.AsEnumerableAsync( - global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory0.Instance).QueryBufferedAsync(param, global::Dapper.RowFactory.Inbuilt.Value())); + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory0.Instance).QueryBufferedAsync(param, RowFactory0.Instance)); } [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DbString.input.cs", 24, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) + internal static global::System.Threading.Tasks.Task> QueryAsync1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { - // Query, Async, TypedResult, HasParameters, Buffered, Text, KnownParameters - // takes parameter: global::Foo.Poco + // Query, Async, TypedResult, HasParameters, Buffered, Text, BindResultsByName, KnownParameters + // takes parameter: global::Foo.QueryModel // parameter map: Id Name - // returns data: int + // returns data: global::Foo.Product global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); global::System.Diagnostics.Debug.Assert(param is not null); return global::Dapper.DapperAotExtensions.AsEnumerableAsync( - global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory1.Instance).QueryBufferedAsync((global::Foo.Poco)param!, global::Dapper.RowFactory.Inbuilt.Value())); + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory1.Instance).QueryBufferedAsync((global::Foo.QueryModel)param!, RowFactory0.Instance)); } @@ -54,6 +54,72 @@ private class CommonCommandFactory : global::Dapper.CommandFactory private static readonly CommonCommandFactory DefaultCommandFactory = new(); + private sealed class RowFactory0 : global::Dapper.RowFactory + { + internal static readonly RowFactory0 Instance = new(); + private RowFactory0() {} + public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) + { + for (int i = 0; i < tokens.Length; i++) + { + int token = -1; + var name = reader.GetName(columnOffset); + var type = reader.GetFieldType(columnOffset); + switch (NormalizedHash(name)) + { + case 2521315361U when NormalizedEquals(name, "productid"): + token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible + break; + case 2369371622U when NormalizedEquals(name, "name"): + token = type == typeof(string) ? 1 : 4; + break; + case 1133313085U when NormalizedEquals(name, "productnumber"): + token = type == typeof(string) ? 2 : 5; + break; + + } + tokens[i] = token; + columnOffset++; + + } + return null; + } + public override global::Foo.Product Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) + { + global::Foo.Product result = new(); + foreach (var token in tokens) + { + switch (token) + { + case 0: + result.ProductId = reader.GetInt32(columnOffset); + break; + case 3: + result.ProductId = GetValue(reader, columnOffset); + break; + case 1: + result.Name = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); + break; + case 4: + result.Name = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); + break; + case 2: + result.ProductNumber = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); + break; + case 5: + result.ProductNumber = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); + break; + + } + columnOffset++; + + } + return result; + + } + + } + private sealed class CommandFactory0 : CommonCommandFactory // { internal static readonly CommandFactory0 Instance = new(); @@ -87,10 +153,10 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje } - private sealed class CommandFactory1 : CommonCommandFactory + private sealed class CommandFactory1 : CommonCommandFactory { internal static readonly CommandFactory1 Instance = new(); - public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.Poco args) + public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.QueryModel args) { var ps = cmd.Parameters; global::System.Data.Common.DbParameter p; @@ -107,7 +173,7 @@ public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global: ps.Add(p); } - public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.Poco args) + public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.QueryModel args) { var ps = cmd.Parameters; global::Dapper.Aot.Generated.DbStringHelpers.ConfigureDbStringDbParameter(ps[0], args.Name); @@ -143,6 +209,9 @@ namespace Dapper.Aot.Generated /// /// Contains helpers to properly handle /// +#if !DAPPERAOT_INTERNAL + file +#endif static class DbStringHelpers { public static void ConfigureDbStringDbParameter( diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.txt b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.txt index 29906b6b..b5a7d042 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.txt +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.txt @@ -1,4 +1,4 @@ Generator produced 1 diagnostics: Hidden DAP000 L1 C1 -Dapper.AOT handled 2 of 2 possible call-sites using 2 interceptors, 2 commands and 0 readers +Dapper.AOT handled 2 of 2 possible call-sites using 2 interceptors, 2 commands and 1 readers diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.txt b/test/Dapper.AOT.Test/Interceptors/DbString.output.txt index 29906b6b..b5a7d042 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.txt +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.txt @@ -1,4 +1,4 @@ Generator produced 1 diagnostics: Hidden DAP000 L1 C1 -Dapper.AOT handled 2 of 2 possible call-sites using 2 interceptors, 2 commands and 0 readers +Dapper.AOT handled 2 of 2 possible call-sites using 2 interceptors, 2 commands and 1 readers From c21fe7d8675aa48e79369e05b4389a7b0229e1e3 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Fri, 26 Jul 2024 11:16:22 +0200 Subject: [PATCH 27/27] use global:: for DapperSpecialType --- src/Dapper.AOT.Analyzers/Internal/Inspection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index 8c13195a..00c4d359 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -504,7 +504,8 @@ public DapperSpecialType DapperSpecialType TypeKind: TypeKind.Class, ContainingNamespace: { - Name: "Dapper" + Name: "Dapper", + IsGlobalNamespace: true } }) return DapperSpecialType.DbString;