diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnectionInternal.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnectionInternal.cs index 3fd58ba6599..27009094932 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnectionInternal.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnectionInternal.cs @@ -93,7 +93,10 @@ public SqliteConnectionInternal(SqliteConnectionStringBuilder connectionOptions, } } - var rc = sqlite3_open_v2(filename, out _db, flags, vfs: null); + var vfs = !string.IsNullOrWhiteSpace(connectionOptions.Vfs) + ? connectionOptions.Vfs + : null; + var rc = sqlite3_open_v2(filename, out _db, flags, vfs: vfs); SqliteException.ThrowExceptionForRC(rc, _db); if (connectionOptions.Password.Length != 0) diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnectionStringBuilder.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnectionStringBuilder.cs index ade9c15e176..3fc9af04f1c 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnectionStringBuilder.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnectionStringBuilder.cs @@ -31,6 +31,7 @@ public class SqliteConnectionStringBuilder : DbConnectionStringBuilder private const string DefaultTimeoutKeyword = "Default Timeout"; private const string CommandTimeoutKeyword = "Command Timeout"; private const string PoolingKeyword = "Pooling"; + private const string VfsKeyword = "Vfs"; private enum Keywords { @@ -41,7 +42,8 @@ private enum Keywords ForeignKeys, RecursiveTriggers, DefaultTimeout, - Pooling + Pooling, + Vfs, } private static readonly IReadOnlyList _validKeywords; @@ -55,10 +57,11 @@ private enum Keywords private bool _recursiveTriggers; private int _defaultTimeout = 30; private bool _pooling = true; + private string? _vfs; static SqliteConnectionStringBuilder() { - var validKeywords = new string[8]; + var validKeywords = new string[9]; validKeywords[(int)Keywords.DataSource] = DataSourceKeyword; validKeywords[(int)Keywords.Mode] = ModeKeyword; validKeywords[(int)Keywords.Cache] = CacheKeyword; @@ -67,9 +70,10 @@ static SqliteConnectionStringBuilder() validKeywords[(int)Keywords.RecursiveTriggers] = RecursiveTriggersKeyword; validKeywords[(int)Keywords.DefaultTimeout] = DefaultTimeoutKeyword; validKeywords[(int)Keywords.Pooling] = PoolingKeyword; + validKeywords[(int)Keywords.Vfs] = VfsKeyword; _validKeywords = validKeywords; - _keywords = new Dictionary(11, StringComparer.OrdinalIgnoreCase) + _keywords = new Dictionary(12, StringComparer.OrdinalIgnoreCase) { [DataSourceKeyword] = Keywords.DataSource, [ModeKeyword] = Keywords.Mode, @@ -79,6 +83,7 @@ static SqliteConnectionStringBuilder() [RecursiveTriggersKeyword] = Keywords.RecursiveTriggers, [DefaultTimeoutKeyword] = Keywords.DefaultTimeout, [PoolingKeyword] = Keywords.Pooling, + [VfsKeyword] = Keywords.Vfs, // aliases [FilenameKeyword] = Keywords.DataSource, @@ -217,6 +222,17 @@ public bool Pooling set => base[PoolingKeyword] = _pooling = value; } + /// + /// Gets or sets the SQLite VFS used by the connection. + /// + /// The SQLite VFS used by the connection. + /// SQLite VFS + public string? Vfs + { + get => _vfs; + set => base[VfsKeyword] = _vfs = value; + } + /// /// Gets or sets the value associated with the specified key. /// @@ -269,6 +285,9 @@ public override object? this[string keyword] case Keywords.Pooling: Pooling = Convert.ToBoolean(value, CultureInfo.InvariantCulture); return; + case Keywords.Vfs: + Vfs = Convert.ToString(value, CultureInfo.InvariantCulture); + return; default: Debug.Fail("Unexpected keyword: " + keyword); @@ -458,6 +477,9 @@ private void Reset(Keywords index) case Keywords.Pooling: _pooling = true; return; + case Keywords.Vfs: + _vfs = null; + return; default: Debug.Fail("Unexpected keyword: " + index); diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionStringBuilderTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionStringBuilderTest.cs index b43cd5f4e26..b7a28de63b5 100644 --- a/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionStringBuilderTest.cs +++ b/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionStringBuilderTest.cs @@ -70,6 +70,14 @@ public void Ctor_parses_DefaultTimeout(string keyword) Assert.Equal(1, builder.DefaultTimeout); } + [Fact] + public void Ctor_parses_Vfs() + { + var builder = new SqliteConnectionStringBuilder("Vfs=win32-longpath"); + + Assert.Equal("win32-longpath", builder.Vfs); + } + [Fact] public void ConnectionString_defaults_to_empty() { @@ -148,7 +156,7 @@ public void Keys_works() var keys = (ICollection)new SqliteConnectionStringBuilder().Keys; Assert.True(keys.IsReadOnly); - Assert.Equal(8, keys.Count); + Assert.Equal(9, keys.Count); Assert.Contains("Data Source", keys); Assert.Contains("Mode", keys); Assert.Contains("Cache", keys); @@ -157,6 +165,7 @@ public void Keys_works() Assert.Contains("Recursive Triggers", keys); Assert.Contains("Default Timeout", keys); Assert.Contains("Pooling", keys); + Assert.Contains("Vfs", keys); } [Fact] @@ -165,7 +174,7 @@ public void Values_works() var values = (ICollection)new SqliteConnectionStringBuilder().Values; Assert.True(values.IsReadOnly); - Assert.Equal(8, values.Count); + Assert.Equal(9, values.Count); } [Fact] diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs index 5639a128262..7fe8c2ff200 100644 --- a/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs +++ b/test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs @@ -364,6 +364,23 @@ public void Open_works_when_recursive_triggers() Assert.Equal(1L, connection.ExecuteScalar("PRAGMA recursive_triggers;")); } + [Fact] + public void Open_works_when_vfs() + { + var vfs = Environment.OSVersion.Platform == PlatformID.Win32NT + ? "win32-longpath" + : "unix-dotfile"; + using var connection = new SqliteConnection($"Data Source=:memory:;Vfs={vfs}"); + connection.Open(); + } + + [Fact] + public void Open_throws_when_vfs_invalid() + { + using var connection = new SqliteConnection("Data Source=:memory:;Vfs=invalidvfs"); + Assert.Throws(connection.Open); + } + [Fact] public void BackupDatabase_works() {