Skip to content

Commit 2f56056

Browse files
authored
- expand auto-sproc detection to handle more scenarios and explicit exclusions (#1989)
- add net7 target - use regex generator when available (net7+) - fix #1984
1 parent cdadfa6 commit 2f56056

File tree

13 files changed

+110
-33
lines changed

13 files changed

+110
-33
lines changed

Dapper.StrongName/Dapper.StrongName.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
<Title>Dapper (Strong Named)</Title>
66
<Description>A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..</Description>
77
<Authors>Sam Saffron;Marc Gravell;Nick Craver</Authors>
8-
<TargetFrameworks>net461;netstandard2.0;net5.0</TargetFrameworks>
8+
<TargetFrameworks>net461;netstandard2.0;net5.0;net7.0</TargetFrameworks>
99
<SignAssembly>true</SignAssembly>
1010
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
1111
<Nullable>enable</Nullable>
1212
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
13+
<DefineConstants>$(DefineConstants);STRONG_NAME</DefineConstants>
1314
</PropertyGroup>
1415
<ItemGroup>
1516
<Folder Include="Properties\" />

Dapper/CommandDefinition.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Data;
33
using System.Reflection;
44
using System.Reflection.Emit;
5-
using System.Text.RegularExpressions;
65
using System.Threading;
76

87
namespace Dapper
@@ -101,17 +100,18 @@ public CommandDefinition(string commandText, object? parameters = null, IDbTrans
101100
CommandTypeDirect = commandType ?? InferCommandType(commandText);
102101
Flags = flags;
103102
CancellationToken = cancellationToken;
104-
105-
static CommandType InferCommandType(string sql)
106-
{
107-
if (sql is null || WhitespaceChars.IsMatch(sql)) return System.Data.CommandType.Text;
108-
return System.Data.CommandType.StoredProcedure;
109-
}
110103
}
111104

112-
// if the sql contains any whitespace character (space/tab/cr/lf/etc - via unicode): interpret as ad-hoc; but "SomeName" should be treated as a stored-proc
113-
// (note TableDirect would need to be specified explicitly, but in reality providers don't usually support TableDirect anyway)
114-
private static readonly Regex WhitespaceChars = new(@"\s", RegexOptions.Compiled);
105+
internal static CommandType InferCommandType(string sql)
106+
{
107+
// if the sql contains any whitespace character (space/tab/cr/lf/etc - via unicode),
108+
// has operators, comments, semi-colon, or a known exception: interpret as ad-hoc;
109+
// otherwise, simple names like "SomeName" should be treated as a stored-proc
110+
// (note TableDirect would need to be specified explicitly, but in reality providers don't usually support TableDirect anyway)
111+
112+
if (sql is null || CompiledRegex.WhitespaceOrReserved.IsMatch(sql)) return System.Data.CommandType.Text;
113+
return System.Data.CommandType.StoredProcedure;
114+
}
115115

116116
private CommandDefinition(object? parameters) : this()
117117
{

Dapper/CompiledRegex.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using System.Text.RegularExpressions;
3+
4+
namespace Dapper;
5+
6+
internal static partial class CompiledRegex
7+
{
8+
#if DEBUG && NET7_0_OR_GREATER // enables colorization in IDE
9+
[StringSyntax("Regex")]
10+
#endif
11+
private const string
12+
WhitespaceOrReservedPattern = @"[\s;/\-+*]|^vacuum$",
13+
LegacyParameterPattern = @"(?<![\p{L}\p{N}@_])[?@:](?![\p{L}\p{N}@_])", // look for ? / @ / : *by itself* - see SupportLegacyParameterTokens
14+
LiteralTokensPattern = @"(?<![\p{L}\p{N}_])\{=([\p{L}\p{N}_]+)\}", // look for {=abc} to inject member abc as a literal
15+
PseudoPositionalPattern = @"\?([\p{L}_][\p{L}\p{N}_]*)\?"; // look for ?abc? for the purpose of subst back to ? using member abc
16+
17+
18+
#if NET7_0_OR_GREATER // use regex code generator (this doesn't work for down-level, even if you define the attribute manually)
19+
[GeneratedRegex(LegacyParameterPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)]
20+
private static partial Regex LegacyParameterGen();
21+
22+
[GeneratedRegex(LiteralTokensPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)]
23+
private static partial Regex LiteralTokensGen();
24+
25+
[GeneratedRegex(PseudoPositionalPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)]
26+
private static partial Regex PseudoPositionalGen();
27+
28+
[GeneratedRegex(WhitespaceOrReservedPattern, RegexOptions.IgnoreCase, "en-US")]
29+
private static partial Regex WhitespaceOrReservedGen();
30+
31+
internal static Regex LegacyParameter => LegacyParameterGen();
32+
internal static Regex LiteralTokens => LiteralTokensGen();
33+
internal static Regex PseudoPositional => PseudoPositionalGen();
34+
internal static Regex WhitespaceOrReserved => WhitespaceOrReservedGen();
35+
#else
36+
internal static Regex LegacyParameter { get; }
37+
= new(LegacyParameterPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled);
38+
internal static Regex LiteralTokens { get; }
39+
= new(LiteralTokensPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled);
40+
internal static Regex PseudoPositional { get; }
41+
= new(PseudoPositionalPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
42+
internal static Regex WhitespaceOrReserved { get; }
43+
= new(WhitespaceOrReservedPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
44+
#endif
45+
}

Dapper/Dapper.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<PackageTags>orm;sql;micro-orm</PackageTags>
66
<Description>A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..</Description>
77
<Authors>Sam Saffron;Marc Gravell;Nick Craver</Authors>
8-
<TargetFrameworks>net461;netstandard2.0;net5.0</TargetFrameworks>
8+
<TargetFrameworks>net461;netstandard2.0;net5.0;net7.0</TargetFrameworks>
99
<Nullable>enable</Nullable>
1010
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1111
</PropertyGroup>

Dapper/Global.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
using System.Runtime.CompilerServices;
2+
#if !STRONG_NAME
3+
[assembly: InternalsVisibleTo("Dapper.Tests")]
4+
#endif
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#nullable enable
2+
Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask
3+
Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable<dynamic!>!
4+
Dapper.SqlMapper.GridReader.ReadUnbufferedAsync<T>() -> System.Collections.Generic.IAsyncEnumerable<T>!
5+
static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable<dynamic!>!
6+
static Dapper.SqlMapper.QueryUnbufferedAsync<T>(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable<T>!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#nullable enable

Dapper/SqlMapper.DapperRow.Descriptor.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ private sealed class DapperRowTypeDescriptionProvider : TypeDescriptionProvider
1313
{
1414
public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance)
1515
=> new DapperRowTypeDescriptor(instance);
16-
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
17-
=> new DapperRowTypeDescriptor(instance);
16+
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object? instance)
17+
=> new DapperRowTypeDescriptor(instance!);
1818
}
1919

2020
//// in theory we could implement this for zero-length results to bind; would require
@@ -57,7 +57,7 @@ AttributeCollection ICustomTypeDescriptor.GetAttributes()
5757

5858
EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => EventDescriptorCollection.Empty;
5959

60-
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty;
60+
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[]? attributes) => EventDescriptorCollection.Empty;
6161

6262
internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table, row);
6363
internal static PropertyDescriptorCollection GetProperties(DapperTable? table, IDictionary<string,object?>? row = null)
@@ -75,9 +75,9 @@ internal static PropertyDescriptorCollection GetProperties(DapperTable? table, I
7575
}
7676
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() => GetProperties(_row);
7777

78-
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) => GetProperties(_row);
78+
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[]? attributes) => GetProperties(_row);
7979

80-
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) => _row;
80+
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor? pd) => _row;
8181
}
8282

8383
private sealed class RowBoundPropertyDescriptor : PropertyDescriptor
@@ -95,10 +95,10 @@ public RowBoundPropertyDescriptor(Type type, string name, int index) : base(name
9595
public override bool ShouldSerializeValue(object component) => ((DapperRow)component).TryGetValue(_index, out _);
9696
public override Type ComponentType => typeof(DapperRow);
9797
public override Type PropertyType => _type;
98-
public override object GetValue(object component)
99-
=> ((DapperRow)component).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value;
100-
public override void SetValue(object component, object? value)
101-
=> ((DapperRow)component).SetValue(_index, value is DBNull ? null : value);
98+
public override object GetValue(object? component)
99+
=> ((DapperRow)component!).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value;
100+
public override void SetValue(object? component, object? value)
101+
=> ((DapperRow)component!).SetValue(_index, value is DBNull ? null : value);
102102
}
103103
}
104104
}

Dapper/SqlMapper.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,7 +1865,7 @@ private static CacheInfo GetCacheInfo(Identity identity, object? exampleParamete
18651865

18661866
private static bool ShouldPassByPosition(string sql)
18671867
{
1868-
return sql?.IndexOf('?') >= 0 && pseudoPositional.IsMatch(sql);
1868+
return sql?.IndexOf('?') >= 0 && CompiledRegex.PseudoPositional.IsMatch(sql);
18691869
}
18701870

18711871
private static void PassByPosition(IDbCommand cmd)
@@ -1882,7 +1882,7 @@ private static void PassByPosition(IDbCommand cmd)
18821882
bool firstMatch = true;
18831883
int index = 0; // use this to spoof names; in most pseudo-positional cases, the name is ignored, however:
18841884
// for "snowflake", the name needs to be incremental i.e. "1", "2", "3"
1885-
cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match =>
1885+
cmd.CommandText = CompiledRegex.PseudoPositional.Replace(cmd.CommandText, match =>
18861886
{
18871887
string key = match.Groups[1].Value;
18881888
if (!consumed.Add(key))
@@ -2386,11 +2386,6 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn
23862386
return list;
23872387
}
23882388

2389-
// look for ? / @ / : *by itself*
2390-
private static readonly Regex smellsLikeOleDb = new(@"(?<![\p{L}\p{N}@_])[?@:](?![\p{L}\p{N}@_])", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
2391-
literalTokens = new(@"(?<![\p{L}\p{N}_])\{=([\p{L}\p{N}_]+)\}", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
2392-
pseudoPositional = new(@"\?([\p{L}_][\p{L}\p{N}_]*)\?", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
2393-
23942389
/// <summary>
23952390
/// Replace all literal tokens with their text form.
23962391
/// </summary>
@@ -2496,9 +2491,9 @@ internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand com
24962491
internal static IList<LiteralToken> GetLiteralTokens(string sql)
24972492
{
24982493
if (string.IsNullOrEmpty(sql)) return LiteralToken.None;
2499-
if (!literalTokens.IsMatch(sql)) return LiteralToken.None;
2494+
if (!CompiledRegex.LiteralTokens.IsMatch(sql)) return LiteralToken.None;
25002495

2501-
var matches = literalTokens.Matches(sql);
2496+
var matches = CompiledRegex.LiteralTokens.Matches(sql);
25022497
var found = new HashSet<string>(StringComparer.Ordinal);
25032498
var list = new List<LiteralToken>(matches.Count);
25042499
foreach (Match match in matches)
@@ -2538,7 +2533,7 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true
25382533

25392534
if (filterParams && Settings.SupportLegacyParameterTokens)
25402535
{
2541-
filterParams = !smellsLikeOleDb.IsMatch(identity.Sql);
2536+
filterParams = !CompiledRegex.LegacyParameter.IsMatch(identity.Sql);
25422537
}
25432538

25442539
var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<IncludeSymbols>false</IncludeSymbols>
2222
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
2323
<PublishRepositoryUrl>true</PublishRepositoryUrl>
24-
<LangVersion>9.0</LangVersion>
24+
<LangVersion>11</LangVersion>
2525
<CheckEolTargetFramework>false</CheckEolTargetFramework>
2626
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
2727
<PackageReadmeFile>readme.md</PackageReadmeFile>

0 commit comments

Comments
 (0)