Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update regex approach and internals re Dapper #68

Merged
merged 1 commit into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
using Dapper.Internal.Roslyn;
using Dapper.SqlAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Data;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using static Dapper.Internal.Inspection;

Expand Down Expand Up @@ -225,7 +223,7 @@ private void ValidateDapperMethod(in OperationAnalysisContext ctx, IOperation sq
var loc = factoryMethod?.Locations.FirstOrDefault() ?? resultMap.ElementType.Locations.FirstOrDefault();
ctx.ReportDiagnostic(Diagnostic.Create(Diagnostics.ConstructorOverridesFactoryMethod, loc, resultMap.ElementType.GetDisplayString()));
}

if (ctorFault is not null)
{
var loc = ctor?.Locations.FirstOrDefault() ?? resultMap.ElementType.Locations.FirstOrDefault();
Expand Down Expand Up @@ -341,9 +339,6 @@ private void ValidatePropertyUsage(in OperationAnalysisContext ctx, IOperation s
ValidateSql(ctx, sqlSource, flags, SqlParameters.None);
}

private static readonly Regex HasWhitespace = new(@"\s", RegexOptions.Compiled | RegexOptions.Multiline);


private readonly SqlSyntax? DefaultSqlSyntax;
private readonly SqlParseInputFlags? DebugSqlFlags;
private readonly Compilation _compilation;
Expand Down Expand Up @@ -394,9 +389,9 @@ private void ValidateSql(in OperationAnalysisContext ctx, IOperation sqlSource,
{
DiagnosticDescriptor? descriptor = stringSyntaxKind switch
{
StringSyntaxKind.InterpolatedString
StringSyntaxKind.InterpolatedString
=> Diagnostics.InterpolatedStringSqlExpression,
StringSyntaxKind.ConcatenatedString or StringSyntaxKind.FormatString
StringSyntaxKind.ConcatenatedString or StringSyntaxKind.FormatString
=> Diagnostics.ConcatenatedStringSqlExpression,
_ => null
};
Expand All @@ -407,7 +402,7 @@ StringSyntaxKind.ConcatenatedString or StringSyntaxKind.FormatString
}
return;
}
if (string.IsNullOrWhiteSpace(sql) || !HasWhitespace.IsMatch(sql)) return; // need non-trivial content to validate
if (string.IsNullOrWhiteSpace(sql) || !CompiledRegex.WhitespaceOrReserved.IsMatch(sql)) return; // need non-trivial content to validate

location ??= ctx.Operation.Syntax.GetLocation();

Expand Down Expand Up @@ -538,7 +533,7 @@ internal static Location SharedParseArgsAndFlags(in ParseState ctx, IInvocationO
{
case null when !string.IsNullOrWhiteSpace(sql):
// if no spaces: interpret as stored proc, else: text
flags |= sql!.Trim().IndexOf(' ') < 0 ? OperationFlags.StoredProcedure : OperationFlags.Text;
flags |= CompiledRegex.WhitespaceOrReserved.IsMatch(sql) ? OperationFlags.Text : OperationFlags.StoredProcedure;
break;
case null:
break; // flexible
Expand Down Expand Up @@ -900,7 +895,7 @@ enum ParameterMode
reportDiagnostic?.Invoke(Diagnostic.Create(Diagnostics.DuplicateParameter,
location: member.GetLocation(Types.DbValueAttribute),
additionalLocations: existing.AsAdditionalLocations(Types.DbValueAttribute),
messageArgs: [existing.CodeName, member.CodeName, dbName ]));
messageArgs: [existing.CodeName, member.CodeName, dbName]));
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,6 @@ internal static class FeatureKeys
public const string InterceptorsPreviewNamespaces = nameof(InterceptorsPreviewNamespaces),
CodegenNamespace = "Dapper.AOT";
public static KeyValuePair<string, string> InterceptorsPreviewNamespacePair => new(InterceptorsPreviewNamespaces, CodegenNamespace);

public static bool IsEnabled(string value)
{
if (string.IsNullOrWhiteSpace(value)) return false;
value = value.Trim();

return match.IsMatch(value);

}
static readonly Regex match = new("(?:;|^)" + CodegenNamespace + "(?:;|$)", RegexOptions.Compiled);
}

private static bool CheckPrerequisites(in GenerateState ctx)
Expand Down
1 change: 1 addition & 0 deletions src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<Compile Update="CodeAnalysis/TypeAccessorInterceptorGenerator.*.cs">
<DependentUpon>TypeAccessorInterceptorGenerator.cs</DependentUpon>
</Compile>
<Compile Include="..\Dapper.AOT\Internal\CompiledRegex.cs" Link="Internal\CompiledRegex.cs" />

<Compile Include="..\Dapper.AOT\Internal\StringHashing.cs" Link="Internal\StringHashing.cs" />
<!--<Compile Include="..\Dapper.AOT\SqlSyntax.cs" Link="SqlSyntax.cs" />-->
Expand Down
2 changes: 1 addition & 1 deletion src/Dapper.AOT/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ internal void Initialize(in UnifiedCommand cmd,
string sql, CommandType commandType, T args)
{
cmd.CommandText = sql;
cmd.CommandType = commandType != 0 ? commandType : sql.IndexOf(' ') >= 0 ? CommandType.Text : CommandType.StoredProcedure; // assume text if at least one space
cmd.CommandType = commandType != 0 ? commandType : DapperAotExtensions.GetCommandType(sql);
AddParameters(in cmd, args);
}

Expand Down
5 changes: 3 additions & 2 deletions src/Dapper.AOT/DapperAotExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Dapper.Internal;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Threading.Tasks;
Expand Down Expand Up @@ -55,6 +56,6 @@ public static Command<T> Command<T>(this IDbTransaction transaction, string sql,
/// Suggest an appropriate command-type for the provided SQL
/// </summary>
public static CommandType GetCommandType(string sql)
=> sql.IndexOf(' ') < 0 ? CommandType.StoredProcedure : CommandType.Text;
=> CompiledRegex.WhitespaceOrReserved.IsMatch(sql) ? CommandType.Text : CommandType.StoredProcedure;
}

45 changes: 45 additions & 0 deletions src/Dapper.AOT/Internal/CompiledRegex.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;

namespace Dapper.Internal;

internal static partial class CompiledRegex
{
#if DEBUG && NET7_0_OR_GREATER // enables colorization in IDE
[StringSyntax("Regex")]
#endif
private const string
WhitespaceOrReservedPattern = @"[\s;/\-+*]|^vacuum$|^commit$|^rollback$",
LegacyParameterPattern = @"(?<![\p{L}\p{N}@_])[?@:](?![\p{L}\p{N}@_])", // look for ? / @ / : *by itself* - see SupportLegacyParameterTokens
LiteralTokensPattern = @"(?<![\p{L}\p{N}_])\{=([\p{L}\p{N}_]+)\}", // look for {=abc} to inject member abc as a literal
PseudoPositionalPattern = @"\?([\p{L}_][\p{L}\p{N}_]*)\?"; // look for ?abc? for the purpose of subst back to ? using member abc


#if NET7_0_OR_GREATER // use regex code generator (this doesn't work for down-level, even if you define the attribute manually)
[GeneratedRegex(LegacyParameterPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)]
private static partial Regex LegacyParameterGen();

[GeneratedRegex(LiteralTokensPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)]
private static partial Regex LiteralTokensGen();

[GeneratedRegex(PseudoPositionalPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)]
private static partial Regex PseudoPositionalGen();

[GeneratedRegex(WhitespaceOrReservedPattern, RegexOptions.IgnoreCase, "en-US")]
private static partial Regex WhitespaceOrReservedGen();

internal static Regex LegacyParameter => LegacyParameterGen();
internal static Regex LiteralTokens => LiteralTokensGen();
internal static Regex PseudoPositional => PseudoPositionalGen();
internal static Regex WhitespaceOrReserved => WhitespaceOrReservedGen();
#else
internal static Regex LegacyParameter { get; }
= new(LegacyParameterPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled);
internal static Regex LiteralTokens { get; }
= new(LiteralTokensPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled);
internal static Regex PseudoPositional { get; }
= new(PseudoPositionalPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
internal static Regex WhitespaceOrReserved { get; }
= new(WhitespaceOrReservedPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
#endif
}
Loading