Commit df77f394 authored by Marc Gravell's avatar Marc Gravell

Add SQLITE to test builds; add tests for issues #466 and #467; fix #466 by...

Add SQLITE to test builds; add tests for issues #466 and #467; fix #466 by adding retry-with-fallback flags strategy
parent 8e9cc49f
{
"profiles": {
"test": {
"commandName": "test",
"sdkVersion": "dnx-clr-win-x86.1.0.0-rc1-update1"
}
}
}
\ No newline at end of file
......@@ -41,6 +41,12 @@
#if POSTGRESQL
using Npgsql;
#endif
#if SQLITE
using Microsoft.Data.Sqlite;
#endif
#if ASYNC
using System.Threading.Tasks;
#endif
#if COREFX
namespace System.ComponentModel {
......@@ -3532,5 +3538,106 @@ public class TPTable
public int Value { get; set; }
}
#endif
#if SQLITE
[FactSqlite]
public void Issue466_SqliteHatesOptimizations()
{
using (var connection = GetSqliteConnection())
{
SqlMapper.ResetTypeHandlers();
var row = connection.Query<HazNameId>("select 42 as Id").First();
row.Id.IsEqualTo(42);
row = connection.Query<HazNameId>("select 42 as Id").First();
row.Id.IsEqualTo(42);
SqlMapper.ResetTypeHandlers();
row = connection.QueryFirst<HazNameId>("select 42 as Id");
row.Id.IsEqualTo(42);
row = connection.QueryFirst<HazNameId>("select 42 as Id");
row.Id.IsEqualTo(42);
}
}
#if ASYNC
[FactSqlite]
public async Task Issue466_SqliteHatesOptimizations_Async()
{
using (var connection = GetSqliteConnection())
{
SqlMapper.ResetTypeHandlers();
var row = (await connection.QueryAsync<HazNameId>("select 42 as Id")).First();
row.Id.IsEqualTo(42);
row = (await connection.QueryAsync<HazNameId>("select 42 as Id")).First();
row.Id.IsEqualTo(42);
SqlMapper.ResetTypeHandlers();
row = await connection.QueryFirstAsync<HazNameId>("select 42 as Id");
row.Id.IsEqualTo(42);
row = await connection.QueryFirstAsync<HazNameId>("select 42 as Id");
row.Id.IsEqualTo(42);
}
}
[FactSqlite]
public void Isse467_SqliteLikesParametersWithPrefix()
{
Isse467_SqliteParameterNaming(true);
}
[FactSqlite]
public void Isse467_SqliteHatesParametersWithoutPrefix()
{ // see issue 375 / 467
#if DNX
try {
Isse467_SqliteParameterNaming(false);
throw new Exception("Expected failure");
} catch(InvalidOperationException ex)
{
ex.Message.IsEqualTo("Must add values for the following parameters: @foo");
}
#else
Isse467_SqliteParameterNaming(false);
#endif
}
private void Isse467_SqliteParameterNaming(bool prefix)
{
using (var connection = GetSqliteConnection())
{
var cmd = connection.CreateCommand();
cmd.CommandText = "select @foo";
cmd.Parameters.Add(prefix ? "@foo" : "foo", SqliteType.Integer).Value = 42;
var i = Convert.ToInt32(cmd.ExecuteScalar());
i.IsEqualTo(42);
}
}
#endif
public class FactSqliteAttribute : FactAttribute
{
public override string Skip
{
get { return unavailable ?? base.Skip; }
set { base.Skip = value; }
}
private static string unavailable;
static FactSqliteAttribute()
{
try
{
using (GetSqliteConnection()) { }
}
catch (Exception ex)
{
unavailable = $"Sqlite is unavailable: {ex.Message}";
}
}
}
protected static SqliteConnection GetSqliteConnection(bool open = true)
{
var connection = new SqliteConnection("Data Source=:memory:");
if (open) connection.Open();
return connection;
}
#endif
}
}
......@@ -36,8 +36,7 @@
"MASSIVE",
"ORMLITE",
"SOMA",
"SIMPLEDATA",
"SQLITE"
"SIMPLEDATA"
]
},
"frameworkAssemblies": {
......@@ -81,8 +80,7 @@
"ORMLITE",
"SOMA",
"SIMPLEDATA",
"XUNIT2",
"SQLITE"
"XUNIT2"
]
},
"frameworkAssemblies": {
......@@ -117,10 +115,11 @@
},
"dotnet5.4": {
"compilationOptions": {
"define": [ "ASYNC", "COREFX" ]
"define": [ "ASYNC", "COREFX", "XUNIT2", "SQLITE" ]
},
"dependencies": {
"Microsoft.CSharp": "4.0.1-*",
"Microsoft.Data.Sqlite": "1.0.0-rc1-final",
"System.Collections": "4.0.11-*",
"System.Console": "4.0.0-*",
"System.Data.SqlClient": "4.0.0-*",
......@@ -147,7 +146,8 @@
"ORMLITE",
"SOMA",
"SIMPLEDATA",
"XUNIT2"
"XUNIT2",
"SQLITE"
]
},
"frameworkAssemblies": {
......@@ -163,6 +163,7 @@
},
"EntityFramework": "6.1.3",
"FirebirdSql.Data.FirebirdClient": "4.10.0",
"Microsoft.Data.Sqlite": "1.0.0-rc1-final",
"Microsoft.SqlServer.Compact": "4.0.8876.1",
"Microsoft.SqlServer.Types": "11.0.2",
"MySql.Data": "6.9.8",
......@@ -183,9 +184,10 @@
},
"dnxcore50": {
"compilationOptions": {
"define": [ "COREFX", "ASYNC", "DNX" ]
"define": [ "COREFX", "ASYNC", "DNX", "XUNIT2", "SQLITE" ]
},
"dependencies": {
"Microsoft.Data.Sqlite": "1.0.0-rc1-final",
"System.Text.RegularExpressions": "4.0.11-*",
"xunit": "2.2.0-beta1-build3239",
"xunit.runner.dnx": "2.1.0-rc1-build204"
......
......@@ -194,6 +194,16 @@ public static Task<object> QuerySingleOrDefaultAsync(this IDbConnection cnn, Typ
return QueryRowAsync<object>(cnn, Row.SingleOrDefault, type, command);
}
private static Task<DbDataReader> ExecuteReaderWithFlagsFallbackAsync(DbCommand cmd, bool wasClosed, CommandBehavior behavior, CancellationToken cancellationToken)
{
var task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken);
if (task.Status == TaskStatus.Faulted && DisableCommandBehaviorOptimizations(behavior, task.Exception.InnerException))
{ // we can retry; this time it will have different flags
task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken);
}
return task;
}
private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
{
object param = command.Parameters;
......@@ -207,7 +217,7 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn,
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(cancel).ConfigureAwait(false);
reader = await cmd.ExecuteReaderAsync(GetBehavior(wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult), cancel).ConfigureAwait(false);
reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false);
var tuple = info.Deserializer;
int hash = GetColumnHash(reader);
......@@ -269,9 +279,9 @@ private static async Task<T> QueryRowAsync<T>(this IDbConnection cnn, Row row, T
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(cancel).ConfigureAwait(false);
reader = await cmd.ExecuteReaderAsync(GetBehavior(wasClosed, (row & Row.Single) != 0
reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, (row & Row.Single) != 0
? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition
: CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow), cancel).ConfigureAwait(false);
: CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancel).ConfigureAwait(false);
T result = default(T);
if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0)
......@@ -644,7 +654,7 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
using (var reader = await cmd.ExecuteReaderAsync(GetBehavior(wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult), command.CancellationToken).ConfigureAwait(false))
using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false))
{
if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true);
......@@ -691,7 +701,7 @@ private static async Task<IEnumerable<TReturn>> MultiMapAsync<TReturn>(this IDbC
try {
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
using (var reader = await cmd.ExecuteReaderAsync(GetBehavior(wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult), command.CancellationToken).ConfigureAwait(false)) {
using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false)) {
var results = MultiMapImpl<TReturn>(null, default(CommandDefinition), types, map, splitOn, reader, identity, true);
return command.Buffered ? results.ToList() : results;
}
......@@ -741,7 +751,7 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn,
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader);
reader = await cmd.ExecuteReaderAsync(GetBehavior(wasClosed, CommandBehavior.SequentialAccess), command.CancellationToken).ConfigureAwait(false);
reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false);
var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters, command.AddToCache, command.CancellationToken);
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
......@@ -818,7 +828,7 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn,
{
cmd = (DbCommand)command.SetupCommand(cnn, paramReader);
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
var reader = await cmd.ExecuteReaderAsync(GetBehavior(wasClosed, CommandBehavior.Default), command.CancellationToken).ConfigureAwait(false);
var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.Default, command.CancellationToken).ConfigureAwait(false);
wasClosed = false;
return reader;
}
......
......@@ -237,6 +237,7 @@ private static void ResetTypeHandlers(bool clone)
{
AddSqlDataRecordsTypeHandler(clone);
} catch { }
allowedCommandBehaviors = DefaultAllowedCommandBehaviors;
#endif
}
#if !COREFX
......@@ -877,7 +878,7 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD
{
if (wasClosed) cnn.Open();
cmd = command.SetupCommand(cnn, info.ParamReader);
reader = cmd.ExecuteReader(GetBehavior(wasClosed, CommandBehavior.SequentialAccess));
reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, CommandBehavior.SequentialAccess);
var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters, command.AddToCache);
cmd = null; // now owned by result
......@@ -900,7 +901,22 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD
throw;
}
}
private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool wasClosed, CommandBehavior behavior)
{
try
{
return cmd.ExecuteReader(GetBehavior(wasClosed, behavior));
}
catch (ArgumentException ex)
{ // thanks, Sqlite!
if (DisableCommandBehaviorOptimizations(behavior, ex))
{
// we can retry; this time it will have different flags
return cmd.ExecuteReader(GetBehavior(wasClosed, behavior));
}
throw;
}
}
private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command, Type effectiveType)
{
object param = command.Parameters;
......@@ -916,7 +932,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
cmd = command.SetupCommand(cnn, info.ParamReader);
if (wasClosed) cnn.Open();
reader = cmd.ExecuteReader(GetBehavior(wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult));
reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult);
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
// with the CloseConnection flag, so the reader will deal with the connection; we
// still need something in the "finally" to ensure that broken SQL still results
......@@ -1005,9 +1021,9 @@ private static T QueryRowImpl<T>(IDbConnection cnn, Row row, ref CommandDefiniti
cmd = command.SetupCommand(cnn, info.ParamReader);
if (wasClosed) cnn.Open();
reader = cmd.ExecuteReader(GetBehavior(wasClosed, (row & Row.Single) != 0
reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, (row & Row.Single) != 0
? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition
: CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow));
: CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow);
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
T result = default(T);
......@@ -1262,7 +1278,7 @@ public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string
{
ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader);
if (wasClosed) cnn.Open();
ownedReader = ownedCommand.ExecuteReader(GetBehavior(wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult));
ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult);
reader = ownedReader;
}
DeserializerState deserializer = default(DeserializerState);
......@@ -1305,9 +1321,25 @@ public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string
}
}
}
const CommandBehavior DefaultAllowedCommandBehaviors = ~((CommandBehavior)0);
static CommandBehavior allowedCommandBehaviors = DefaultAllowedCommandBehaviors;
private static bool DisableCommandBehaviorOptimizations(CommandBehavior behavior, Exception ex)
{
if(allowedCommandBehaviors == DefaultAllowedCommandBehaviors
&& (behavior & (CommandBehavior.SingleResult | CommandBehavior.SingleRow)) != 0)
{
if (ex.Message.Contains(nameof(CommandBehavior.SingleResult))
|| ex.Message.Contains(nameof(CommandBehavior.SingleRow)))
{ // some providers just just allow these, so: try again without them and stop issuing them
allowedCommandBehaviors = ~(CommandBehavior.SingleResult | CommandBehavior.SingleRow);
return true;
}
}
return false;
}
private static CommandBehavior GetBehavior(bool close, CommandBehavior @default)
{
return close ? (@default | CommandBehavior.CloseConnection) : @default;
return (close ? (@default | CommandBehavior.CloseConnection) : @default) & allowedCommandBehaviors;
}
static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn, CommandDefinition command, Type[] types, Func<object[], TReturn> map, string splitOn, IDataReader reader, Identity identity, bool finalize)
{
......@@ -1330,7 +1362,7 @@ static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn, Comman
{
ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader);
if (wasClosed) cnn.Open();
ownedReader = ownedCommand.ExecuteReader(GetBehavior(wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult));
ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult);
reader = ownedReader;
}
DeserializerState deserializer;
......@@ -2522,7 +2554,7 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin
{
cmd = command.SetupCommand(cnn, paramReader);
if (wasClosed) cnn.Open();
var reader = cmd.ExecuteReader(GetBehavior(wasClosed, commandBehavior));
var reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, commandBehavior);
wasClosed = false; // don't dispose before giving it to them!
disposeCommand = false;
// note: command.FireOutputCallbacks(); would be useless here; parameters come at the **end** of the TDS stream
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment