Skip to content
This repository was archived by the owner on Nov 1, 2018. It is now read-only.

Commit 2ab86d4

Browse files
author
Nate McMaster
committed
Support SQLite back to 3.7.9
Fallback to older sqlite3 calls when using older versions of sqlite3. Fallback to sqlite3_close when sqlite3_close_v2 is not available, e.g. older versions of OSX. Workaround for missing sqlite3_errstr in versions < 3.7.15 Add OSX to test matrix.
1 parent 397ab72 commit 2ab86d4

File tree

12 files changed

+187
-18
lines changed

12 files changed

+187
-18
lines changed

.travis.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
language: csharp
22
sudo: false
3+
env:
4+
- MONO_THREADS_PER_CPU=2000 MONO_MANAGED_WATCHER=disabled
35
mono:
46
- beta
57
os:
68
- linux
9+
- osx
710

811
addons:
912
apt:
10-
sources:
11-
- debian-sid
1213
packages:
1314
- libunwind8
14-
- sqlite3
15-
15+
1616
before_script:
1717
- sqlite3 -version
18+
before_install:
19+
- if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install icu4c; fi
1820
script:
1921
- ./build.sh --quiet verify

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Contains SQLite implementations of the System.Data.Common interfaces.
1010
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.
1111

1212
## Requirements
13-
Requires SQLite >= 3.7.15
13+
Requires SQLite >= 3.7.9
1414

1515
This library binds to the native SQLite library. On some systems, you must also install separately the SQLite library.
1616

src/Microsoft.Data.Sqlite/Interop/MarshalEx.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,7 @@ public static void ThrowExceptionForRC(int rc, Sqlite3Handle db)
6060
return;
6161
}
6262

63-
var message = db == null || db.IsInvalid
64-
? NativeMethods.sqlite3_errstr(rc)
65-
: NativeMethods.sqlite3_errmsg(db);
66-
67-
throw new SqliteException(Strings.FormatSqliteNativeError(rc, message), rc);
63+
throw new SqliteException(VersionedMethods.SqliteErrorMessage(rc, db), rc);
6864
}
6965
}
7066
}

src/Microsoft.Data.Sqlite/Interop/NativeMethods.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ public static int sqlite3_bind_text(Sqlite3StmtHandle pStmt, int i, string data,
9292
[DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
9393
public static extern int sqlite3_close_v2(IntPtr db);
9494

95+
[DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl)]
96+
public static extern int sqlite3_close(IntPtr db);
97+
9598
[DllImport("sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)]
9699
private static extern IntPtr sqlite3_column_blob_raw(Sqlite3StmtHandle pStmt, int iCol);
97100

src/Microsoft.Data.Sqlite/Interop/Sqlite3Handle.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ private Sqlite3Handle()
1717

1818
protected override bool ReleaseHandle()
1919
{
20-
var rc = NativeMethods.sqlite3_close_v2(handle);
20+
var rc = VersionedMethods.SqliteClose(handle);
2121
handle = IntPtr.Zero;
2222

2323
return rc == Constants.SQLITE_OK;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.Data.Sqlite.Interop
7+
{
8+
internal class VersionedMethods
9+
{
10+
public static string SqliteErrorMessage(int rc, Sqlite3Handle db)
11+
{
12+
var message = db == null || db.IsInvalid
13+
? _strategy.ErrorString(rc)
14+
: NativeMethods.sqlite3_errmsg(db);
15+
16+
return Strings.FormatSqliteNativeError(rc, message);
17+
}
18+
19+
public static int SqliteClose(IntPtr handle)
20+
=> _strategy.Close(handle);
21+
22+
public static string SqliteDbFilename(Sqlite3Handle db, string databaseName)
23+
=> _strategy.DbFilename(db, databaseName);
24+
25+
private static readonly StrategyBase _strategy = GetStrategy(new Version(NativeMethods.sqlite3_libversion()));
26+
27+
private static StrategyBase GetStrategy(Version current)
28+
{
29+
if (current >= new Version("3.7.15"))
30+
{
31+
return new Strategy3_7_15();
32+
}
33+
if (current >= new Version("3.7.14"))
34+
{
35+
return new Strategy3_7_14();
36+
}
37+
if (current >= new Version("3.7.10"))
38+
{
39+
return new Strategy3_7_10();
40+
}
41+
return new StrategyBase();
42+
}
43+
44+
private class Strategy3_7_15 : Strategy3_7_14
45+
{
46+
public override string ErrorString(int rc)
47+
=> NativeMethods.sqlite3_errstr(rc) + " " + base.ErrorString(rc);
48+
}
49+
50+
private class Strategy3_7_14 : Strategy3_7_10
51+
{
52+
public override int Close(IntPtr handle)
53+
=> NativeMethods.sqlite3_close_v2(handle);
54+
}
55+
56+
private class Strategy3_7_10 : StrategyBase
57+
{
58+
public override string DbFilename(Sqlite3Handle db, string databaseName)
59+
=> NativeMethods.sqlite3_db_filename(db, databaseName);
60+
}
61+
62+
private class StrategyBase
63+
{
64+
public virtual string ErrorString(int rc)
65+
=> Strings.DefaultNativeError;
66+
67+
public virtual int Close(IntPtr handle)
68+
=> NativeMethods.sqlite3_close(handle);
69+
70+
public virtual string DbFilename(Sqlite3Handle db, string databaseName)
71+
=> null;
72+
}
73+
}
74+
}

src/Microsoft.Data.Sqlite/Properties/Strings.Designer.cs

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Data.Sqlite/SqliteConnection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public override string ConnectionString
6262

6363
public override string DataSource =>
6464
State == ConnectionState.Open
65-
? NativeMethods.sqlite3_db_filename(_db, MainDatabaseName)
65+
? VersionedMethods.SqliteDbFilename(_db, MainDatabaseName) ?? ConnectionStringBuilder.DataSource
6666
: ConnectionStringBuilder.DataSource;
6767

6868
/// <summary>

src/Microsoft.Data.Sqlite/Strings.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@
181181
<value>No mapping exists from object type {typeName} to a known managed provider native type.</value>
182182
</data>
183183
<data name="SqliteNativeError" xml:space="preserve">
184-
<value>SQLite Error {errorCode}: '{message}'</value>
184+
<value>SQLite Error {errorCode}: '{message}'.</value>
185+
</data>
186+
<data name="DefaultNativeError" xml:space="preserve">
187+
<value>For more information on this error code see https://www.sqlite.org/rescode.html</value>
185188
</data>
186189
</root>

test/Microsoft.Data.Sqlite.Tests/SqliteConcurrencyTest.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,12 @@ public void It_throws_sqlite_busy_on_deadlock()
116116
var ex = Assert.Throws<SqliteException>(() => dropCommand.ExecuteNonQuery());
117117

118118
Assert.Equal(SQLITE_BUSY, ex.SqliteErrorCode);
119-
var message = NativeMethods.sqlite3_errstr(SQLITE_BUSY);
120-
Assert.Equal(Strings.FormatSqliteNativeError(SQLITE_BUSY, message), ex.Message);
119+
120+
if (CurrentVersion >= new Version("3.7.15"))
121+
{
122+
var message = NativeMethods.sqlite3_errstr(SQLITE_BUSY);
123+
Assert.Equal(Strings.FormatSqliteNativeError(SQLITE_BUSY, message), ex.Message);
124+
}
121125
}
122126

123127
dropCommand.ExecuteNonQuery();
@@ -191,8 +195,12 @@ public void Command_times_out()
191195
waitHandle.Release();
192196

193197
Assert.Equal(SQLITE_BUSY, ex.SqliteErrorCode);
194-
var message = NativeMethods.sqlite3_errstr(SQLITE_BUSY);
195-
Assert.Equal(Strings.FormatSqliteNativeError(SQLITE_BUSY, message), ex.Message);
198+
199+
if (CurrentVersion >= new Version("3.7.15"))
200+
{
201+
var message = NativeMethods.sqlite3_errstr(SQLITE_BUSY);
202+
Assert.Equal(Strings.FormatSqliteNativeError(SQLITE_BUSY, message), ex.Message);
203+
}
196204
});
197205

198206
t1.Start();
@@ -202,6 +210,8 @@ public void Command_times_out()
202210
}
203211
}
204212
}
213+
214+
private Version CurrentVersion => new Version(NativeMethods.sqlite3_libversion());
205215

206216
private const string FileName = "./concurrency.db";
207217

test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Data;
66
using System.IO;
7+
using Microsoft.AspNet.Testing.xunit;
78
using Xunit;
89

910
namespace Microsoft.Data.Sqlite
@@ -60,7 +61,8 @@ public void DataSource_returns_connection_string_data_source_when_closed()
6061
Assert.Equal("test.db", connection.DataSource);
6162
}
6263

63-
[Fact]
64+
[ConditionalFact]
65+
[SqliteVersionCondition(Min = "3.7.10")]
6466
public void DataSource_returns_actual_filename_when_open()
6567
{
6668
using (var connection = new SqliteConnection("Data Source=test.db"))
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.Data.Sqlite.Interop;
6+
using Microsoft.AspNet.Testing.xunit;
7+
8+
namespace Microsoft.Data.Sqlite
9+
{
10+
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
11+
internal class SqliteVersionConditionAttribute : Attribute, ITestCondition
12+
{
13+
private Version _min;
14+
private Version _max;
15+
private Version _skip;
16+
public string Min { get { return _min.ToString(); } set { _min = new Version(value); } }
17+
public string Max { get { return _max.ToString(); } set { _max = new Version(value); } }
18+
public string Skip { get { return _skip.ToString(); } set { _skip = new Version(value); } }
19+
20+
private Version Current = new Version(NativeMethods.sqlite3_libversion());
21+
22+
public bool IsMet
23+
{
24+
get
25+
{
26+
if (Current == _skip)
27+
{
28+
return false;
29+
}
30+
31+
if (_min == null && _max == null)
32+
{
33+
return true;
34+
}
35+
36+
if (_min == null)
37+
{
38+
return Current <= _max;
39+
}
40+
41+
if (_max == null)
42+
{
43+
return Current >= _min;
44+
}
45+
46+
return Current <= _max && Current >= _min;
47+
}
48+
}
49+
50+
private string _skipReason;
51+
52+
public string SkipReason
53+
{
54+
set { _skipReason = value; }
55+
get
56+
{
57+
return _skipReason ??
58+
$"Test only runs for SQLite versions >= { Min ?? "Any"} and <= { Max ?? "Any" }"
59+
+ (Skip == null ? "" : "and skipping on " + Skip);
60+
}
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)