diff --git a/.travis.yml b/.travis.yml index 68dd3110..4f2bb406 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,21 @@ language: csharp sudo: false +env: + - MONO_THREADS_PER_CPU=2000 MONO_MANAGED_WATCHER=disabled mono: - beta os: - linux + - osx addons: apt: - sources: - - debian-sid packages: - libunwind8 - - sqlite3 - + before_script: - sqlite3 -version +before_install: + - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install icu4c; fi script: - ./build.sh --quiet verify diff --git a/README.md b/README.md index 190bad1d..9cf87891 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Contains SQLite implementations of the System.Data.Common interfaces. This project is part of ASP.NET 5. You can find samples, documentation and getting started instructions for ASP.NET 5 at the [Home](https://github.com/aspnet/home) repo. ## Requirements -Requires SQLite >= 3.7.15 +Requires SQLite >= 3.7.9 This library binds to the native SQLite library. On some systems, you must also install separately the SQLite library. diff --git a/src/Microsoft.Data.Sqlite/Interop/MarshalEx.cs b/src/Microsoft.Data.Sqlite/Interop/MarshalEx.cs index eadfc98a..9a0ad18f 100644 --- a/src/Microsoft.Data.Sqlite/Interop/MarshalEx.cs +++ b/src/Microsoft.Data.Sqlite/Interop/MarshalEx.cs @@ -60,11 +60,7 @@ public static void ThrowExceptionForRC(int rc, Sqlite3Handle db) return; } - var message = db == null || db.IsInvalid - ? NativeMethods.sqlite3_errstr(rc) - : NativeMethods.sqlite3_errmsg(db); - - throw new SqliteException(Strings.FormatSqliteNativeError(rc, message), rc); + throw new SqliteException(VersionedMethods.SqliteErrorMessage(rc, db), rc); } } } diff --git a/src/Microsoft.Data.Sqlite/Interop/NativeMethods.cs b/src/Microsoft.Data.Sqlite/Interop/NativeMethods.cs index bf68ff70..68b9cc92 100644 --- a/src/Microsoft.Data.Sqlite/Interop/NativeMethods.cs +++ b/src/Microsoft.Data.Sqlite/Interop/NativeMethods.cs @@ -92,6 +92,9 @@ public static int sqlite3_bind_text(Sqlite3StmtHandle pStmt, int i, string data, [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)] public static extern int sqlite3_close_v2(IntPtr db); + [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)] + public static extern int sqlite3_close(IntPtr db); + [DllImport("sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr sqlite3_column_blob_raw(Sqlite3StmtHandle pStmt, int iCol); diff --git a/src/Microsoft.Data.Sqlite/Interop/Sqlite3Handle.cs b/src/Microsoft.Data.Sqlite/Interop/Sqlite3Handle.cs index 37ab6b6d..2319338f 100644 --- a/src/Microsoft.Data.Sqlite/Interop/Sqlite3Handle.cs +++ b/src/Microsoft.Data.Sqlite/Interop/Sqlite3Handle.cs @@ -17,7 +17,7 @@ private Sqlite3Handle() protected override bool ReleaseHandle() { - var rc = NativeMethods.sqlite3_close_v2(handle); + var rc = VersionedMethods.SqliteClose(handle); handle = IntPtr.Zero; return rc == Constants.SQLITE_OK; diff --git a/src/Microsoft.Data.Sqlite/Interop/VersionedMethods.cs b/src/Microsoft.Data.Sqlite/Interop/VersionedMethods.cs new file mode 100644 index 00000000..86bdc50f --- /dev/null +++ b/src/Microsoft.Data.Sqlite/Interop/VersionedMethods.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Data.Sqlite.Interop +{ + internal class VersionedMethods + { + public static string SqliteErrorMessage(int rc, Sqlite3Handle db) + { + var message = db == null || db.IsInvalid + ? _strategy.ErrorString(rc) + : NativeMethods.sqlite3_errmsg(db); + + return Strings.FormatSqliteNativeError(rc, message); + } + + public static int SqliteClose(IntPtr handle) + => _strategy.Close(handle); + + public static string SqliteDbFilename(Sqlite3Handle db, string databaseName) + => _strategy.DbFilename(db, databaseName); + + private static readonly StrategyBase _strategy = GetStrategy(new Version(NativeMethods.sqlite3_libversion())); + + private static StrategyBase GetStrategy(Version current) + { + if (current >= new Version("3.7.15")) + { + return new Strategy3_7_15(); + } + if (current >= new Version("3.7.14")) + { + return new Strategy3_7_14(); + } + if (current >= new Version("3.7.10")) + { + return new Strategy3_7_10(); + } + return new StrategyBase(); + } + + private class Strategy3_7_15 : Strategy3_7_14 + { + public override string ErrorString(int rc) + => NativeMethods.sqlite3_errstr(rc) + " " + base.ErrorString(rc); + } + + private class Strategy3_7_14 : Strategy3_7_10 + { + public override int Close(IntPtr handle) + => NativeMethods.sqlite3_close_v2(handle); + } + + private class Strategy3_7_10 : StrategyBase + { + public override string DbFilename(Sqlite3Handle db, string databaseName) + => NativeMethods.sqlite3_db_filename(db, databaseName); + } + + private class StrategyBase + { + public virtual string ErrorString(int rc) + => Strings.DefaultNativeError; + + public virtual int Close(IntPtr handle) + => NativeMethods.sqlite3_close(handle); + + public virtual string DbFilename(Sqlite3Handle db, string databaseName) + => null; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Data.Sqlite/Properties/Strings.Designer.cs b/src/Microsoft.Data.Sqlite/Properties/Strings.Designer.cs index ecb21cc9..c0e15786 100644 --- a/src/Microsoft.Data.Sqlite/Properties/Strings.Designer.cs +++ b/src/Microsoft.Data.Sqlite/Properties/Strings.Designer.cs @@ -362,6 +362,22 @@ internal static string FormatSqliteNativeError(object errorCode, object message) return string.Format(CultureInfo.CurrentCulture, GetString("SqliteNativeError", "errorCode", "message"), errorCode, message); } + /// + /// For more information on this error code see https://www.sqlite.org/rescode.html + /// + internal static string DefaultNativeError + { + get { return GetString("DefaultNativeError"); } + } + + /// + /// For more information on this error code see https://www.sqlite.org/rescode.html + /// + internal static string FormatDefaultNativeError() + { + return GetString("DefaultNativeError"); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.Data.Sqlite/SqliteConnection.cs b/src/Microsoft.Data.Sqlite/SqliteConnection.cs index c6e00252..24f17d1e 100644 --- a/src/Microsoft.Data.Sqlite/SqliteConnection.cs +++ b/src/Microsoft.Data.Sqlite/SqliteConnection.cs @@ -62,7 +62,7 @@ public override string ConnectionString public override string DataSource => State == ConnectionState.Open - ? NativeMethods.sqlite3_db_filename(_db, MainDatabaseName) + ? VersionedMethods.SqliteDbFilename(_db, MainDatabaseName) ?? ConnectionStringBuilder.DataSource : ConnectionStringBuilder.DataSource; /// diff --git a/src/Microsoft.Data.Sqlite/Strings.resx b/src/Microsoft.Data.Sqlite/Strings.resx index 301d752c..3b747e38 100644 --- a/src/Microsoft.Data.Sqlite/Strings.resx +++ b/src/Microsoft.Data.Sqlite/Strings.resx @@ -181,6 +181,9 @@ No mapping exists from object type {typeName} to a known managed provider native type. - SQLite Error {errorCode}: '{message}' + SQLite Error {errorCode}: '{message}'. + + + For more information on this error code see https://www.sqlite.org/rescode.html \ No newline at end of file diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteConcurrencyTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteConcurrencyTest.cs index 9f8d5205..d86202d3 100644 --- a/test/Microsoft.Data.Sqlite.Tests/SqliteConcurrencyTest.cs +++ b/test/Microsoft.Data.Sqlite.Tests/SqliteConcurrencyTest.cs @@ -116,8 +116,12 @@ public void It_throws_sqlite_busy_on_deadlock() var ex = Assert.Throws(() => dropCommand.ExecuteNonQuery()); Assert.Equal(SQLITE_BUSY, ex.SqliteErrorCode); - var message = NativeMethods.sqlite3_errstr(SQLITE_BUSY); - Assert.Equal(Strings.FormatSqliteNativeError(SQLITE_BUSY, message), ex.Message); + + if (CurrentVersion >= new Version("3.7.15")) + { + var message = NativeMethods.sqlite3_errstr(SQLITE_BUSY); + Assert.Equal(Strings.FormatSqliteNativeError(SQLITE_BUSY, message), ex.Message); + } } dropCommand.ExecuteNonQuery(); @@ -191,8 +195,12 @@ public void Command_times_out() waitHandle.Release(); Assert.Equal(SQLITE_BUSY, ex.SqliteErrorCode); - var message = NativeMethods.sqlite3_errstr(SQLITE_BUSY); - Assert.Equal(Strings.FormatSqliteNativeError(SQLITE_BUSY, message), ex.Message); + + if (CurrentVersion >= new Version("3.7.15")) + { + var message = NativeMethods.sqlite3_errstr(SQLITE_BUSY); + Assert.Equal(Strings.FormatSqliteNativeError(SQLITE_BUSY, message), ex.Message); + } }); t1.Start(); @@ -202,6 +210,8 @@ public void Command_times_out() } } } + + private Version CurrentVersion => new Version(NativeMethods.sqlite3_libversion()); private const string FileName = "./concurrency.db"; diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs index c360ec06..0fab3ae2 100644 --- a/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs +++ b/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs @@ -4,6 +4,7 @@ using System; using System.Data; using System.IO; +using Microsoft.AspNet.Testing.xunit; using Xunit; namespace Microsoft.Data.Sqlite @@ -60,7 +61,8 @@ public void DataSource_returns_connection_string_data_source_when_closed() Assert.Equal("test.db", connection.DataSource); } - [Fact] + [ConditionalFact] + [SqliteVersionCondition(Min = "3.7.10")] public void DataSource_returns_actual_filename_when_open() { using (var connection = new SqliteConnection("Data Source=test.db")) diff --git a/test/Microsoft.Data.Sqlite.Tests/TestUtilities/SqliteVersionConditionAttribute.cs b/test/Microsoft.Data.Sqlite.Tests/TestUtilities/SqliteVersionConditionAttribute.cs new file mode 100644 index 00000000..a748c15a --- /dev/null +++ b/test/Microsoft.Data.Sqlite.Tests/TestUtilities/SqliteVersionConditionAttribute.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Data.Sqlite.Interop; +using Microsoft.AspNet.Testing.xunit; + +namespace Microsoft.Data.Sqlite +{ + [AttributeUsage(AttributeTargets.Method, Inherited = true)] + internal class SqliteVersionConditionAttribute : Attribute, ITestCondition + { + private Version _min; + private Version _max; + private Version _skip; + public string Min { get { return _min.ToString(); } set { _min = new Version(value); } } + public string Max { get { return _max.ToString(); } set { _max = new Version(value); } } + public string Skip { get { return _skip.ToString(); } set { _skip = new Version(value); } } + + private Version Current = new Version(NativeMethods.sqlite3_libversion()); + + public bool IsMet + { + get + { + if (Current == _skip) + { + return false; + } + + if (_min == null && _max == null) + { + return true; + } + + if (_min == null) + { + return Current <= _max; + } + + if (_max == null) + { + return Current >= _min; + } + + return Current <= _max && Current >= _min; + } + } + + private string _skipReason; + + public string SkipReason + { + set { _skipReason = value; } + get + { + return _skipReason ?? + $"Test only runs for SQLite versions >= { Min ?? "Any"} and <= { Max ?? "Any" }" + + (Skip == null ? "" : "and skipping on " + Skip); + } + } + } +} \ No newline at end of file