diff --git a/Dapper NET40/SqlMapper.cs b/Dapper NET40/SqlMapper.cs index b40826ddfa731527e339d38a31a7056c138c1389..e46b6466acad01eeb96479166aba360e4a526b40 100644 --- a/Dapper NET40/SqlMapper.cs +++ b/Dapper NET40/SqlMapper.cs @@ -47,7 +47,7 @@ public enum CommandFlags /// /// Should the plan cache be bypassed? /// - NoCache = 4 + NoCache = 4, } /// /// Represents the key aspects of a sql operation @@ -2044,18 +2044,14 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter info = new CacheInfo(); if (identity.parametersType != null) { + Action reader; if (exampleParameters is IDynamicParameters) { - info.ParamReader = (cmd, obj) => { ((IDynamicParameters)obj).AddParameters(cmd, identity); }; + reader = (cmd, obj) => { ((IDynamicParameters)obj).AddParameters(cmd, identity); }; } -#if CSHARP30 else if (exampleParameters is IEnumerable>) -#else - // special-case dictionary and `dynamic` - else if (exampleParameters is IEnumerable> || exampleParameters is System.Dynamic.IDynamicMetaObjectProvider) -#endif { - info.ParamReader = (cmd, obj) => + reader = (cmd, obj) => { IDynamicParameters mapped = new DynamicParameters(obj); mapped.AddParameters(cmd, identity); @@ -2064,14 +2060,67 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter else { var literals = GetLiteralTokens(identity.sql); - info.ParamReader = CreateParamInfoGenerator(identity, false, true, literals); + reader = CreateParamInfoGenerator(identity, false, true, literals); } + if((identity.commandType == null || identity.commandType == CommandType.Text) && ShouldPassByPosition(identity.sql)) + { + var tail = reader; + var sql = identity.sql; + reader = (cmd, obj) => + { + tail(cmd, obj); + PassByPosition(cmd); + }; + } + info.ParamReader = reader; } if(addToCache) SetQueryCache(identity, info); } return info; } + private static bool ShouldPassByPosition(string sql) + { + return sql != null && sql.IndexOf('?') >= 0 && pseudoPositional.IsMatch(sql); + } + + private static void PassByPosition(IDbCommand cmd) + { + if (cmd.Parameters.Count == 0) return; + + Dictionary parameters = new Dictionary(StringComparer.InvariantCulture); + + foreach(IDbDataParameter param in cmd.Parameters) + { + if (!string.IsNullOrEmpty(param.ParameterName)) parameters[param.ParameterName] = param; + } + HashSet consumed = new HashSet(StringComparer.InvariantCulture); + cmd.Parameters.Clear(); + + cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match => + { + string key = match.Groups[1].Value; + IDbDataParameter param; + if (!consumed.Add(key)) + { + throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once"); + } + else if (parameters.TryGetValue(key, out param)) + { + // if found, return the anonymous token "?" + cmd.Parameters.Add(param); + parameters.Remove(key); + consumed.Add(key); + return "?"; + } + else + { + // otherwise, leave alone for simple debugging + return match.Value; + } + }); + } + private static Func GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { #if !CSHARP30 @@ -2682,14 +2731,14 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj private static IEnumerable FilterParameters(IEnumerable parameters, string sql) { - return parameters.Where(p => Regex.IsMatch(sql, @"[?@:]" + p.Name + "([^a-zA-Z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)); + return parameters.Where(p => Regex.IsMatch(sql, @"[?@:]" + p.Name + "([^a-z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)); } - // look for ? / @ / : *by itself* - static readonly Regex smellsLikeOleDb = new Regex(@"(? /// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql /// @@ -4280,13 +4329,7 @@ public DynamicParameters(object template) /// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic /// /// - public void AddDynamicParams( -#if CSHARP30 -object param -#else -dynamic param -#endif -) + public void AddDynamicParams(object param) { var obj = param as object; if (obj != null) @@ -4304,11 +4347,7 @@ dynamic param { foreach (var kvp in dictionary) { -#if CSHARP30 Add(kvp.Key, kvp.Value, null, null, null); -#else - Add(kvp.Key, kvp.Value); -#endif } } } diff --git a/Tests/Assert.cs b/Tests/Assert.cs index 67ab422515cbcfe8ed542fbd34fc4bb53fe5a06c..f9f19a9c09a8e49d5082b89eb96a2a7147b2a674 100644 --- a/Tests/Assert.cs +++ b/Tests/Assert.cs @@ -24,6 +24,10 @@ public static void IsSequenceEqualTo(this IEnumerable obj, IEnumerable } } + public static void Fail() + { + throw new ApplicationException("Expectation failed"); + } public static void IsFalse(this bool b) { if (b) diff --git a/Tests/DapperTests NET40.csproj b/Tests/DapperTests NET40.csproj index e3fba3eb92a0e24be5aa54d9b07f731289886210..ee6ef2a013e338ead829fa94b6248008b94ebfe8 100644 --- a/Tests/DapperTests NET40.csproj +++ b/Tests/DapperTests NET40.csproj @@ -47,6 +47,9 @@ ..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll + + ..\packages\FirebirdSql.Data.FirebirdClient.4.5.1.0\lib\net45\FirebirdSql.Data.FirebirdClient.dll + False ..\packages\FSharp.Core.4.0.0\lib\FSharp.Core.dll @@ -264,6 +267,7 @@ + diff --git a/Tests/Tests.cs b/Tests/Tests.cs index d7763451fde8d35673ec6b87da553c5a9cf54bdd..40c2664af2174a12507c58c90dca13f7785cfb5e 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -18,6 +18,7 @@ using System.Data.Entity.Spatial; using Microsoft.SqlServer.Types; using System.Data.SqlTypes; +using FirebirdSql.Data.FirebirdClient; #if POSTGRESQL using Npgsql; #endif @@ -498,7 +499,7 @@ public void TestExtraFields() // see http://stackoverflow.com/q/18847510/23354 public void TestOleDbParameters() { - using (var conn = new System.Data.OleDb.OleDbConnection(Program.OleDbConnectionString)) + using (var conn = ConnectViaOledb()) { var row = conn.Query("select Id = ?, Age = ?", new { foo = 12, bar = 23 } // these names DO NOT MATTER!!! @@ -510,6 +511,13 @@ public void TestOleDbParameters() } } + System.Data.OleDb.OleDbConnection ConnectViaOledb() + { + var conn = new System.Data.OleDb.OleDbConnection(Program.OleDbConnectionString); + conn.Open(); + return conn; + } + public void TestStrongType() { var guid = Guid.NewGuid(); @@ -3851,6 +3859,131 @@ public void AllowIDictionaryParameters() connection.Query("SELECT @param1", parameters); } + + + public void Issue178_SqlServer() + { + const string sql = @"select count(*) from Issue178"; + try { connection.Execute("drop table Issue178"); } catch { } + try { connection.Execute("create table Issue178(id int not null)"); } catch { } + // raw ADO.net + var sqlCmd = new SqlCommand(sql, connection); + using (IDataReader reader1 = sqlCmd.ExecuteReader()) + { + Assert.IsTrue(reader1.Read()); + reader1.GetInt32(0).IsEqualTo(0); + Assert.IsFalse(reader1.Read()); + Assert.IsFalse(reader1.NextResult()); + } + + // dapper + using (var reader2 = connection.ExecuteReader(sql)) + { + Assert.IsTrue(reader2.Read()); + reader2.GetInt32(0).IsEqualTo(0); + Assert.IsFalse(reader2.Read()); + Assert.IsFalse(reader2.NextResult()); + } + } + + public void Issue178_Firebird() // we expect this to fail due to a bug in Firebird; a PR to fix it has been submitted + { + var cs = @"initial catalog=localhost:database;user id=SYSDBA;password=masterkey"; + + using (var connection = new FbConnection(cs)) + { + connection.Open(); + const string sql = @"select count(*) from Issue178"; + try { connection.Execute("drop table Issue178"); } catch { } + try { connection.Execute("create table Issue178(id int not null)"); } catch { } + // raw ADO.net + var sqlCmd = new FbCommand(sql, connection); + using (IDataReader reader1 = sqlCmd.ExecuteReader()) + { + Assert.IsTrue(reader1.Read()); + reader1.GetInt32(0).IsEqualTo(0); + Assert.IsFalse(reader1.Read()); + Assert.IsFalse(reader1.NextResult()); + } + + // dapper + using (var reader2 = connection.ExecuteReader(sql)) + { + Assert.IsTrue(reader2.Read()); + reader2.GetInt32(0).IsEqualTo(0); + Assert.IsFalse(reader2.Read()); + Assert.IsFalse(reader2.NextResult()); + } + } + } + + public void PseudoPositionalParameters_Simple() + { + using (var connection = ConnectViaOledb()) + { + int value = connection.Query("select ?x? + ?y_2? + ?z?", new { x = 1, y_2 = 3, z = 5, z2 = 24 }).Single(); + value.IsEqualTo(9); + } + } + public void PseudoPositionalParameters_Dynamic() + { + using (var connection = ConnectViaOledb()) + { + var args = new DynamicParameters(); + args.Add("x", 1); + args.Add("y_2", 3); + args.Add("z", 5); + args.Add("z2", 24); + int value = connection.Query("select ?x? + ?y_2? + ?z?", args).Single(); + value.IsEqualTo(9); + } + } + public void PseudoPositionalParameters_ReusedParameter() + { + using (var connection = ConnectViaOledb()) + { + try + { + int value = connection.Query("select ?x? + ?y_2? + ?x?", new { x = 1, y_2 = 3 }).Single(); + Assert.Fail(); + } + catch (InvalidOperationException ex) + { + ex.Message.IsEqualTo("When passing parameters by position, each parameter can only be referenced once"); + } + } + } + + public void PseudoPositionalParameters_ExecSingle() + { + using (var connection = ConnectViaOledb()) + { + var data = new { x = 6 }; + connection.Execute("create table #named_single(val int not null)"); + int count = connection.Execute("insert #named_single (val) values (?x?)", data); + int sum = (int)connection.ExecuteScalar("select sum(val) from #named_single"); + count.IsEqualTo(1); + sum.IsEqualTo(6); + } + } + public void PseudoPositionalParameters_ExecMulti() + { + using (var connection = ConnectViaOledb()) + { + var data = new[] + { + new { x = 1, y = 1 }, + new { x = 3, y = 1 }, + new { x = 6, y = 1 }, + }; + connection.Execute("create table #named_multi(val int not null)"); + int count = connection.Execute("insert #named_multi (val) values (?x?)", data); + int sum = (int)connection.ExecuteScalar("select sum(val) from #named_multi"); + count.IsEqualTo(3); + sum.IsEqualTo(10); + } + } + #if POSTGRESQL class Cat diff --git a/Tests/packages.config b/Tests/packages.config index 666be145f2096ced3b1876e9b0ce9ca7ea6852ad..bcaaf7693604a569c49cc6fb6c16e6fae509f855 100644 --- a/Tests/packages.config +++ b/Tests/packages.config @@ -1,6 +1,7 @@  + diff --git a/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net40-client/FirebirdSql.Data.FirebirdClient.dll b/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net40-client/FirebirdSql.Data.FirebirdClient.dll new file mode 100644 index 0000000000000000000000000000000000000000..cf21151825f07cb2148918b09efc1fe6629edbe0 Binary files /dev/null and b/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net40-client/FirebirdSql.Data.FirebirdClient.dll differ diff --git a/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net40-client/FirebirdSql.Data.FirebirdClient.pdb b/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net40-client/FirebirdSql.Data.FirebirdClient.pdb new file mode 100644 index 0000000000000000000000000000000000000000..0097c7a95a751ae269ab7f831f38cfda7a783129 Binary files /dev/null and b/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net40-client/FirebirdSql.Data.FirebirdClient.pdb differ diff --git a/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net45/FirebirdSql.Data.FirebirdClient.dll b/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net45/FirebirdSql.Data.FirebirdClient.dll new file mode 100644 index 0000000000000000000000000000000000000000..c63b88a2e862e713b594fdf61ccd5257b6388ebd Binary files /dev/null and b/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net45/FirebirdSql.Data.FirebirdClient.dll differ diff --git a/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net45/FirebirdSql.Data.FirebirdClient.pdb b/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net45/FirebirdSql.Data.FirebirdClient.pdb new file mode 100644 index 0000000000000000000000000000000000000000..1d56e3eb0f48f0f45876351a8c665129ac7ea02c Binary files /dev/null and b/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/lib/net45/FirebirdSql.Data.FirebirdClient.pdb differ diff --git a/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/readme.txt b/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..85ba85bacaa29fd6115b1078d92fd12ce1bf79bd --- /dev/null +++ b/packages/FirebirdSql.Data.FirebirdClient.4.5.1.0/readme.txt @@ -0,0 +1,35 @@ +README - FirebirdClient ADO.NET 2.0 Data provider for Firebird +============================================================== + +This project is supported by: +----------------------------- + + Sean Leyne (Broadview Software) + SMS-Timing + + +Developement list +----------------- + +You can subscribe to the developement list at: + + http://lists.sourceforge.net/lists/listinfo/firebird-net-provider + + +You can access to the lastest developement sources through SVN, see: + + https://sourceforge.net/p/firebird/NETProvider/ + + +Reporting Bugs +-------------- + +Yo can report bugs using two ways: + +1. Sending it to the developement list. +2. Thru the Firebird Project tracker (category DNET): + + + http://tracker.firebirdsql.org/DNET + +