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

auto-detect stored procedures as anything without whitespace #1975

Merged
merged 3 commits into from
Oct 10, 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
22 changes: 18 additions & 4 deletions Dapper/CommandDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,15 @@ internal void OnCompleted()
/// </summary>
public int? CommandTimeout { get; }

internal readonly CommandType CommandTypeDirect;

/// <summary>
/// The type of command that the command-text represents
/// </summary>
public CommandType? CommandType { get; }
#if DEBUG // prevent use in our own code
[Obsolete("Prefer " + nameof(CommandTypeDirect), true)]
#endif
public CommandType? CommandType => CommandTypeDirect;

/// <summary>
/// Should data be buffered before returning?
Expand Down Expand Up @@ -92,11 +97,21 @@ public CommandDefinition(string commandText, object? parameters = null, IDbTrans
Parameters = parameters;
Transaction = transaction;
CommandTimeout = commandTimeout;
CommandType = commandType;
CommandTypeDirect = commandType ?? InferCommandType(commandText);
Flags = flags;
CancellationToken = cancellationToken;

static CommandType InferCommandType(string sql)
{
if (sql is null || sql.IndexOfAny(WhitespaceChars) >= 0) return System.Data.CommandType.Text;
return System.Data.CommandType.StoredProcedure;
}
}

// if the sql contains any whitespace character (space/tab/cr/lf): 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 char[] WhitespaceChars = new char[] { ' ', '\t', '\r', '\n' };

private CommandDefinition(object? parameters) : this()
{
Parameters = parameters;
Expand Down Expand Up @@ -124,8 +139,7 @@ internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object?>?
{
cmd.CommandTimeout = SqlMapper.Settings.CommandTimeout.Value;
}
if (CommandType.HasValue)
cmd.CommandType = CommandType.Value;
cmd.CommandType = CommandTypeDirect;
paramReader?.Invoke(cmd, Parameters);
return cmd;
}
Expand Down
2 changes: 1 addition & 1 deletion Dapper/DynamicParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ internal static bool ShouldSetDbType(DbType dbType)
/// <param name="identity">Information about the query</param>
protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
var literals = SqlMapper.GetLiteralTokens(identity.sql);
var literals = SqlMapper.GetLiteralTokens(identity.Sql);

if (templates is not null)
{
Expand Down
5 changes: 5 additions & 0 deletions Dapper/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,17 @@ override Dapper.SqlMapper.Identity.ToString() -> string!
override Dapper.SqlMapper.StringTypeHandler<T>.Parse(object! value) -> T
override Dapper.SqlMapper.StringTypeHandler<T>.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void
readonly Dapper.SqlMapper.Identity.commandType -> System.Data.CommandType?
Dapper.SqlMapper.Identity.CommandType.get -> System.Data.CommandType?
readonly Dapper.SqlMapper.Identity.connectionString -> string!
readonly Dapper.SqlMapper.Identity.gridIndex -> int
Dapper.SqlMapper.Identity.GridIndex.get -> int
readonly Dapper.SqlMapper.Identity.hashCode -> int
readonly Dapper.SqlMapper.Identity.parametersType -> System.Type?
Dapper.SqlMapper.Identity.ParametersType.get -> System.Type?
readonly Dapper.SqlMapper.Identity.sql -> string!
Dapper.SqlMapper.Identity.Sql.get -> string!
readonly Dapper.SqlMapper.Identity.type -> System.Type?
Dapper.SqlMapper.Identity.Type.get -> System.Type?
static Dapper.DbString.IsAnsiDefault.get -> bool
static Dapper.DbString.IsAnsiDefault.set -> void
static Dapper.DefaultTypeMap.MatchNamesWithUnderscores.get -> bool
Expand Down
16 changes: 8 additions & 8 deletions Dapper/SqlMapper.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, ID
private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
{
object? param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
var cancel = command.CancellationToken;
Expand Down Expand Up @@ -477,7 +477,7 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn,
private static async Task<T> QueryRowAsync<T>(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command)
{
object? param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
var cancel = command.CancellationToken;
Expand Down Expand Up @@ -652,7 +652,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD

private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object? param)
{
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType());
var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader);
Expand Down Expand Up @@ -930,7 +930,7 @@ public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFo
private static async Task<IEnumerable<TReturn>> MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn)
{
object? param = command.Parameters;
var identity = new Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType());
var identity = new Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(command.CommandText, command.CommandTypeDirect, cnn, typeof(TFirst), param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
try
Expand Down Expand Up @@ -979,7 +979,7 @@ private static async Task<IEnumerable<TReturn>> MultiMapAsync<TReturn>(this IDbC
}

object? param = command.Parameters;
var identity = new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types);
var identity = new IdentityWithTypes(command.CommandText, command.CommandTypeDirect, cnn, types[0], param?.GetType(), types);
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
try
Expand Down Expand Up @@ -1029,7 +1029,7 @@ public static Task<GridReader> QueryMultipleAsync(this IDbConnection cnn, string
public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command)
{
object? param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType());
var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, typeof(GridReader), param?.GetType());
CacheInfo info = GetCacheInfo(identity, param, command.AddToCache);

DbCommand? cmd = null;
Expand Down Expand Up @@ -1227,7 +1227,7 @@ private static async Task<DbDataReader> ExecuteWrappedReaderImplAsync(IDbConnect
object? param = command.Parameters;
if (param is not null)
{
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType());
var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param.GetType());
paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader;
}

Expand Down Expand Up @@ -1296,7 +1296,7 @@ static async IAsyncEnumerable<T> Impl(IDbConnection cnn, Type effectiveType, Com
[EnumeratorCancellation] CancellationToken cancel)
{
object? param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader);
Expand Down
62 changes: 61 additions & 1 deletion Dapper/SqlMapper.Identity.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Data;
using System.Runtime.CompilerServices;

Expand Down Expand Up @@ -92,6 +93,7 @@ public class Identity : IEquatable<Identity>

internal virtual Type GetType(int index) => throw new IndexOutOfRangeException(nameof(index));

#pragma warning disable CS0618 // Type or member is obsolete
internal Identity ForGrid<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(Type primaryType, int gridIndex) =>
new Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(sql, commandType, connectionString, primaryType, parametersType, gridIndex);

Expand All @@ -110,12 +112,14 @@ internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) =>
/// <returns></returns>
public Identity ForDynamicParameters(Type type) =>
new Identity(sql, commandType, connectionString, this.type, type, 0, -1);
#pragma warning restore CS0618 // Type or member is obsolete

internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type? type, Type? parametersType)
: this(sql, commandType, connection.ConnectionString, type, parametersType, 0, 0) { /* base call */ }

private protected Identity(string sql, CommandType? commandType, string connectionString, Type? type, Type? parametersType, int otherTypesHash, int gridIndex)
{
#pragma warning disable CS0618 // Type or member is obsolete
this.sql = sql;
this.commandType = commandType;
this.connectionString = connectionString;
Expand All @@ -133,6 +137,7 @@ private protected Identity(string sql, CommandType? commandType, string connecti
hashCode = (hashCode * 23) + (connectionString is null ? 0 : connectionStringComparer.GetHashCode(connectionString));
hashCode = (hashCode * 23) + (parametersType?.GetHashCode() ?? 0);
}
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand All @@ -144,48 +149,101 @@ private protected Identity(string sql, CommandType? commandType, string connecti
/// <summary>
/// The raw SQL command.
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use " + nameof(Sql) + ". This API may be removed at a later date.")]
public readonly string sql;

/// <summary>
/// The raw SQL command.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
public string Sql => sql;
#pragma warning restore CS0618 // Type or member is obsolete

/// <summary>
/// The SQL command type.
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use " + nameof(CommandType) + ". This API may be removed at a later date.")]
public readonly CommandType? commandType;

/// <summary>
/// The SQL command type.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
public CommandType? CommandType => commandType;
#pragma warning restore CS0618 // Type or member is obsolete

/// <summary>
/// The hash code of this Identity.
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use " + nameof(GetHashCode) + ". This API may be removed at a later date.")]
public readonly int hashCode;

/// <summary>
/// The grid index (position in the reader) of this Identity.
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use " + nameof(GridIndex) + ". This API may be removed at a later date.")]
public readonly int gridIndex;

/// <summary>
/// This <see cref="Type"/> of this Identity.
/// The grid index (position in the reader) of this Identity.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
public int GridIndex => gridIndex;
#pragma warning restore CS0618 // Type or member is obsolete

/// <summary>
/// The <see cref="Type"/> of this Identity.
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use " + nameof(Type) + ". This API may be removed at a later date.")]
public readonly Type? type;

/// <summary>
/// The <see cref="Type"/> of this Identity.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
public Type? Type => type;
#pragma warning restore CS0618 // Type or member is obsolete

/// <summary>
/// The connection string for this Identity.
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This API may be removed at a later date.")]
public readonly string connectionString;

/// <summary>
/// The type of the parameters object for this Identity.
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use " + nameof(ParametersType) + ". This API may be removed at a later date.")]
public readonly Type? parametersType;

/// <summary>
/// The type of the parameters object for this Identity.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
public Type? ParametersType => parametersType;
#pragma warning restore CS0618 // Type or member is obsolete

/// <summary>
/// Gets the hash code for this identity.
/// </summary>
/// <returns></returns>
#pragma warning disable CS0618 // Type or member is obsolete
public override int GetHashCode() => hashCode;
#pragma warning restore CS0618 // Type or member is obsolete

/// <summary>
/// See object.ToString()
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
public override string ToString() => sql;
#pragma warning restore CS0618 // Type or member is obsolete

/// <summary>
/// Compare 2 Identity objects
Expand All @@ -198,6 +256,7 @@ public bool Equals(Identity? other)
if (other is null) return false;

int typeCount;
#pragma warning disable CS0618 // Type or member is obsolete
return gridIndex == other.gridIndex
&& type == other.type
&& sql == other.sql
Expand All @@ -206,6 +265,7 @@ public bool Equals(Identity? other)
&& parametersType == other.parametersType
&& (typeCount = TypeCount) == other.TypeCount
&& (typeCount == 0 || TypesEqual(this, other, typeCount));
#pragma warning restore CS0618 // Type or member is obsolete
}

[MethodImpl(MethodImplOptions.NoInlining)]
Expand Down
Loading