diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj
index dec6f7f0c..ef28e32b4 100644
--- a/Dapper.StrongName/Dapper.StrongName.csproj
+++ b/Dapper.StrongName/Dapper.StrongName.csproj
@@ -5,11 +5,12 @@
Dapper (Strong Named)
A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..
Sam Saffron;Marc Gravell;Nick Craver
- net461;netstandard2.0;net5.0
+ net461;netstandard2.0;net5.0;net7.0
true
true
enable
true
+ $(DefineConstants);STRONG_NAME
diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs
index f7132ed52..cc117c304 100644
--- a/Dapper/CommandDefinition.cs
+++ b/Dapper/CommandDefinition.cs
@@ -2,7 +2,6 @@
using System.Data;
using System.Reflection;
using System.Reflection.Emit;
-using System.Text.RegularExpressions;
using System.Threading;
namespace Dapper
@@ -101,17 +100,18 @@ public CommandDefinition(string commandText, object? parameters = null, IDbTrans
CommandTypeDirect = commandType ?? InferCommandType(commandText);
Flags = flags;
CancellationToken = cancellationToken;
-
- static CommandType InferCommandType(string sql)
- {
- if (sql is null || WhitespaceChars.IsMatch(sql)) return System.Data.CommandType.Text;
- return System.Data.CommandType.StoredProcedure;
- }
}
- // 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
- // (note TableDirect would need to be specified explicitly, but in reality providers don't usually support TableDirect anyway)
- private static readonly Regex WhitespaceChars = new(@"\s", RegexOptions.Compiled);
+ internal static CommandType InferCommandType(string sql)
+ {
+ // if the sql contains any whitespace character (space/tab/cr/lf/etc - via unicode),
+ // has operators, comments, semi-colon, or a known exception: interpret as ad-hoc;
+ // otherwise, simple names like "SomeName" should be treated as a stored-proc
+ // (note TableDirect would need to be specified explicitly, but in reality providers don't usually support TableDirect anyway)
+
+ if (sql is null || CompiledRegex.WhitespaceOrReserved.IsMatch(sql)) return System.Data.CommandType.Text;
+ return System.Data.CommandType.StoredProcedure;
+ }
private CommandDefinition(object? parameters) : this()
{
diff --git a/Dapper/CompiledRegex.cs b/Dapper/CompiledRegex.cs
new file mode 100644
index 000000000..bbf857d56
--- /dev/null
+++ b/Dapper/CompiledRegex.cs
@@ -0,0 +1,45 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text.RegularExpressions;
+
+namespace Dapper;
+
+internal static partial class CompiledRegex
+{
+#if DEBUG && NET7_0_OR_GREATER // enables colorization in IDE
+ [StringSyntax("Regex")]
+#endif
+ private const string
+ WhitespaceOrReservedPattern = @"[\s;/\-+*]|^vacuum$",
+ LegacyParameterPattern = @"(? 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
+}
diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj
index cf98d5abf..8fc9c65f8 100644
--- a/Dapper/Dapper.csproj
+++ b/Dapper/Dapper.csproj
@@ -5,7 +5,7 @@
orm;sql;micro-orm
A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..
Sam Saffron;Marc Gravell;Nick Craver
- net461;netstandard2.0;net5.0
+ net461;netstandard2.0;net5.0;net7.0
enable
true
diff --git a/Dapper/Global.cs b/Dapper/Global.cs
new file mode 100644
index 000000000..f6b5f4ef4
--- /dev/null
+++ b/Dapper/Global.cs
@@ -0,0 +1,4 @@
+using System.Runtime.CompilerServices;
+#if !STRONG_NAME
+[assembly: InternalsVisibleTo("Dapper.Tests")]
+#endif
diff --git a/Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt
new file mode 100644
index 000000000..5da46e604
--- /dev/null
+++ b/Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt
@@ -0,0 +1,6 @@
+#nullable enable
+Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask
+Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable!
+Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable!
+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!
+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!
\ No newline at end of file
diff --git a/Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000..91b0e1a43
--- /dev/null
+++ b/Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
\ No newline at end of file
diff --git a/Dapper/SqlMapper.DapperRow.Descriptor.cs b/Dapper/SqlMapper.DapperRow.Descriptor.cs
index 11e85c02d..0824c6658 100644
--- a/Dapper/SqlMapper.DapperRow.Descriptor.cs
+++ b/Dapper/SqlMapper.DapperRow.Descriptor.cs
@@ -13,8 +13,8 @@ private sealed class DapperRowTypeDescriptionProvider : TypeDescriptionProvider
{
public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance)
=> new DapperRowTypeDescriptor(instance);
- public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
- => new DapperRowTypeDescriptor(instance);
+ public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object? instance)
+ => new DapperRowTypeDescriptor(instance!);
}
//// in theory we could implement this for zero-length results to bind; would require
@@ -57,7 +57,7 @@ AttributeCollection ICustomTypeDescriptor.GetAttributes()
EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => EventDescriptorCollection.Empty;
- EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty;
+ EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[]? attributes) => EventDescriptorCollection.Empty;
internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table, row);
internal static PropertyDescriptorCollection GetProperties(DapperTable? table, IDictionary? row = null)
@@ -75,9 +75,9 @@ internal static PropertyDescriptorCollection GetProperties(DapperTable? table, I
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() => GetProperties(_row);
- PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) => GetProperties(_row);
+ PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[]? attributes) => GetProperties(_row);
- object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) => _row;
+ object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor? pd) => _row;
}
private sealed class RowBoundPropertyDescriptor : PropertyDescriptor
@@ -95,10 +95,10 @@ public RowBoundPropertyDescriptor(Type type, string name, int index) : base(name
public override bool ShouldSerializeValue(object component) => ((DapperRow)component).TryGetValue(_index, out _);
public override Type ComponentType => typeof(DapperRow);
public override Type PropertyType => _type;
- public override object GetValue(object component)
- => ((DapperRow)component).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value;
- public override void SetValue(object component, object? value)
- => ((DapperRow)component).SetValue(_index, value is DBNull ? null : value);
+ public override object GetValue(object? component)
+ => ((DapperRow)component!).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value;
+ public override void SetValue(object? component, object? value)
+ => ((DapperRow)component!).SetValue(_index, value is DBNull ? null : value);
}
}
}
diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs
index 797b833d2..fb6ac88e6 100644
--- a/Dapper/SqlMapper.cs
+++ b/Dapper/SqlMapper.cs
@@ -1865,7 +1865,7 @@ private static CacheInfo GetCacheInfo(Identity identity, object? exampleParamete
private static bool ShouldPassByPosition(string sql)
{
- return sql?.IndexOf('?') >= 0 && pseudoPositional.IsMatch(sql);
+ return sql?.IndexOf('?') >= 0 && CompiledRegex.PseudoPositional.IsMatch(sql);
}
private static void PassByPosition(IDbCommand cmd)
@@ -1882,7 +1882,7 @@ private static void PassByPosition(IDbCommand cmd)
bool firstMatch = true;
int index = 0; // use this to spoof names; in most pseudo-positional cases, the name is ignored, however:
// for "snowflake", the name needs to be incremental i.e. "1", "2", "3"
- cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match =>
+ cmd.CommandText = CompiledRegex.PseudoPositional.Replace(cmd.CommandText, match =>
{
string key = match.Groups[1].Value;
if (!consumed.Add(key))
@@ -2386,11 +2386,6 @@ private static IEnumerable FilterParameters(IEnumerable
/// Replace all literal tokens with their text form.
///
@@ -2496,9 +2491,9 @@ internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand com
internal static IList GetLiteralTokens(string sql)
{
if (string.IsNullOrEmpty(sql)) return LiteralToken.None;
- if (!literalTokens.IsMatch(sql)) return LiteralToken.None;
+ if (!CompiledRegex.LiteralTokens.IsMatch(sql)) return LiteralToken.None;
- var matches = literalTokens.Matches(sql);
+ var matches = CompiledRegex.LiteralTokens.Matches(sql);
var found = new HashSet(StringComparer.Ordinal);
var list = new List(matches.Count);
foreach (Match match in matches)
@@ -2538,7 +2533,7 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true
if (filterParams && Settings.SupportLegacyParameterTokens)
{
- filterParams = !smellsLikeOleDb.IsMatch(identity.Sql);
+ filterParams = !CompiledRegex.LegacyParameter.IsMatch(identity.Sql);
}
var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);
diff --git a/Directory.Build.props b/Directory.Build.props
index d858cf494..e9ebe949c 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -21,7 +21,7 @@
false
true
true
- 9.0
+ 11
false
true
readme.md
diff --git a/appveyor.yml b/appveyor.yml
index 122b20fab..a39d87789 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -6,6 +6,9 @@ skip_commits:
files:
- '**/*.md'
+install:
+ - choco install dotnet-sdk --version 7.0.402
+
environment:
Appveyor: true
# Postgres
diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj
index f1e808301..8a003f7bd 100644
--- a/tests/Dapper.Tests/Dapper.Tests.csproj
+++ b/tests/Dapper.Tests/Dapper.Tests.csproj
@@ -2,7 +2,7 @@
Dapper.Tests
Dapper Core Test Suite
- net472;net6.0
+ net472;net6.0;net7.0
$(DefineConstants);MSSQLCLIENT
$(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208
enable
diff --git a/tests/Dapper.Tests/ProcedureTests.cs b/tests/Dapper.Tests/ProcedureTests.cs
index 9cc71f474..be8dbe746 100644
--- a/tests/Dapper.Tests/ProcedureTests.cs
+++ b/tests/Dapper.Tests/ProcedureTests.cs
@@ -317,5 +317,27 @@ public async Task Issue1986_AutoProc_Whitespace(string space)
var result = await connection.QuerySingleAsync(sql);
Assert.Equal(42, result);
}
+
+ [Theory]
+ [InlineData("foo", CommandType.StoredProcedure)]
+ [InlineData("foo;", CommandType.Text)]
+ [InlineData("foo bar", CommandType.Text)]
+ [InlineData("foo bar;", CommandType.Text)]
+ [InlineData("vacuum", CommandType.Text)]
+ [InlineData("vacuum;", CommandType.Text)]
+ [InlineData("FOO", CommandType.StoredProcedure)]
+ [InlineData("FOO;", CommandType.Text)]
+ [InlineData("FOO BAR", CommandType.Text)]
+ [InlineData("FOO BAR;", CommandType.Text)]
+ [InlineData("VACUUM", CommandType.Text)]
+ [InlineData("VACUUM;", CommandType.Text)]
+
+ // comments imply text
+ [InlineData("foo--bar", CommandType.Text)]
+ [InlineData("foo/*bar*/", CommandType.Text)]
+ public void InferCommandType(string sql, CommandType commandType)
+ {
+ Assert.Equal(commandType, CommandDefinition.InferCommandType(sql));
+ }
}
}