From b62269873672e15d2a4e7f95805ba7fc7922211a Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 17 Nov 2023 07:58:03 +0000 Subject: [PATCH] fix #79 --- .../SqlAnalysis/TSqlProcessor.cs | 43 +++++++++++-------- test/Dapper.AOT.Test/Verifiers/DAP025.cs | 13 ++++++ test/Dapper.AOT.Test/Verifiers/DAP026.cs | 13 ++++++ test/Dapper.AOT.Test/Verifiers/Verifier.cs | 6 ++- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/Dapper.AOT.Analyzers/SqlAnalysis/TSqlProcessor.cs b/src/Dapper.AOT.Analyzers/SqlAnalysis/TSqlProcessor.cs index 32e74ab0..a6cc36c7 100644 --- a/src/Dapper.AOT.Analyzers/SqlAnalysis/TSqlProcessor.cs +++ b/src/Dapper.AOT.Analyzers/SqlAnalysis/TSqlProcessor.cs @@ -755,33 +755,37 @@ public override void Visit(SelectStatement node) } index++; } + if (reads != 0) { if (sets != 0) { parser.OnSelectAssignAndRead(spec); } - bool firstQuery = AddQuery(); - if (firstQuery && SingleRow // optionally enforce single-row validation - && spec.FromClause is not null) // no "from" is things like 'select @id, @name' - always one row + if (node.Into is null) // otherwise not actually a query { - bool haveTopOrFetch = false; - if (spec.TopRowFilter is { Percent: false, Expression: ScalarExpression top }) + bool firstQuery = AddQuery(); + if (firstQuery && SingleRow // optionally enforce single-row validation + && spec.FromClause is not null) // no "from" is things like 'select @id, @name' - always one row { - haveTopOrFetch = EnforceTop(top); - } - else if (spec.OffsetClause is { FetchExpression: ScalarExpression fetch }) - { - haveTopOrFetch = EnforceTop(fetch); - } + bool haveTopOrFetch = false; + if (spec.TopRowFilter is { Percent: false, Expression: ScalarExpression top }) + { + haveTopOrFetch = EnforceTop(top); + } + else if (spec.OffsetClause is { FetchExpression: ScalarExpression fetch }) + { + haveTopOrFetch = EnforceTop(fetch); + } - // we want *either* a WHERE (which we will allow with/without a TOP), - // or a TOP + ORDER BY - if (!IsUnfiltered(spec.FromClause, spec.WhereClause)) { } // fine - else if (haveTopOrFetch && spec.OrderByClause is not null) { } // fine - else - { - parser.OnSelectSingleRowWithoutWhere(node); + // we want *either* a WHERE (which we will allow with/without a TOP), + // or a TOP + ORDER BY + if (!IsUnfiltered(spec.FromClause, spec.WhereClause)) { } // fine + else if (haveTopOrFetch && spec.OrderByClause is not null) { } // fine + else + { + parser.OnSelectSingleRowWithoutWhere(node); + } } } } @@ -1340,6 +1344,9 @@ public override void ExplicitVisit(BinaryExpression node) public override void Visit(OutputClause node) { + // note that this doesn't handle OUTPUT INTO, + // which is via OutputIntoClause + AddQuery(); // works like a query base.Visit(node); } diff --git a/test/Dapper.AOT.Test/Verifiers/DAP025.cs b/test/Dapper.AOT.Test/Verifiers/DAP025.cs index 51a5ac09..2746fb75 100644 --- a/test/Dapper.AOT.Test/Verifiers/DAP025.cs +++ b/test/Dapper.AOT.Test/Verifiers/DAP025.cs @@ -46,4 +46,17 @@ exec SomeLookupFetch Diagnostic(Diagnostics.ExecuteCommandWithQuery).WithLocation(0), ]); + [Fact] + public Task ReportWhenNoQueryExpected() => SqlVerifyAsync(""" + SELECT [Test] + FROM MyTable + """, SqlAnalysis.SqlParseInputFlags.ExpectNoQuery, + Diagnostic(Diagnostics.ExecuteCommandWithQuery).WithLocation(Execute)); + + [Fact] // false positive scenario, https://github.com/DapperLib/DapperAOT/issues/79 + public Task DoNotReportSelectInto() => SqlVerifyAsync(""" + SELECT [Test] + INTO #MyTemporaryTable FROM MyTable + """, SqlAnalysis.SqlParseInputFlags.ExpectNoQuery); + } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Verifiers/DAP026.cs b/test/Dapper.AOT.Test/Verifiers/DAP026.cs index 5df09bf6..a1c4746c 100644 --- a/test/Dapper.AOT.Test/Verifiers/DAP026.cs +++ b/test/Dapper.AOT.Test/Verifiers/DAP026.cs @@ -47,4 +47,17 @@ exec SomeLookupInsert 'North' Diagnostic(Diagnostics.QueryCommandMissingQuery).WithLocation(0), ]); + [Fact] + public Task DoNotReportWhenQueryExpected() => SqlVerifyAsync(""" + SELECT [Test] + FROM MyTable + """, SqlAnalysis.SqlParseInputFlags.ExpectQuery); + + [Fact] + public Task ReportSelectInto() => SqlVerifyAsync(""" + SELECT [Test] + INTO #MyTemporaryTable FROM MyTable + """, SqlAnalysis.SqlParseInputFlags.ExpectQuery, + Diagnostic(Diagnostics.QueryCommandMissingQuery).WithLocation(Execute)); + } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Verifiers/Verifier.cs b/test/Dapper.AOT.Test/Verifiers/Verifier.cs index 6b718bcb..7bc115b0 100644 --- a/test/Dapper.AOT.Test/Verifiers/Verifier.cs +++ b/test/Dapper.AOT.Test/Verifiers/Verifier.cs @@ -172,7 +172,9 @@ protected static Func WithVisualBasicParseOptions { internal Task SqlVerifyAsync(string sql, params DiagnosticResult[] expected) => SqlVerifyAsync(sql, SqlParseInputFlags.None, expected); - + + public const int Execute = 9999; + internal Task SqlVerifyAsync(string sql, SqlParseInputFlags sqlParseInputFlags, params DiagnosticResult[] expected) { var cs = $$""" @@ -182,7 +184,7 @@ internal Task SqlVerifyAsync(string sql, SqlParseInputFlags sqlParseInputFlags, [DapperAot(false)] class SomeCode { - public void Foo(DbConnection conn) => conn.Execute(@"{{sql.Replace("\"", "\"\"")}}"); + public void Foo(DbConnection conn) => conn.{|#{{Execute}}:Execute|}(@"{{sql.Replace("\"", "\"\"")}}"); } """; return CSVerifyAsync(cs, DefaultConfig, expected, SqlSyntax.SqlServer, sqlParseInputFlags | SqlParseInputFlags.DebugMode);