From b974366cf7f2766fed36b7900ceeaf8d79828d1d Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 11 Feb 2025 15:10:16 +0100 Subject: [PATCH 1/2] Improve LoadExtension to work correctly with dotnet run and lib packages --- .../SqliteConnection.cs | 68 ++++++++++++++++++- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs index 427e8c56e94..aa293c0a991 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs @@ -48,6 +48,8 @@ public partial class SqliteConnection : DbConnection private static readonly StateChangeEventArgs _fromClosedToOpenEventArgs = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open); private static readonly StateChangeEventArgs _fromOpenToClosedEventArgs = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed); + private static string[]? NativeDllSearchDirectories; + static SqliteConnection() { Type.GetType("SQLitePCL.Batteries_V2, SQLitePCLRaw.batteries_v2") @@ -624,11 +626,71 @@ public virtual void LoadExtension(string file, string? proc = null) private void LoadExtensionCore(string file, string? proc) { - var rc = sqlite3_load_extension(Handle, utf8z.FromString(file), utf8z.FromString(proc), out var errmsg); - if (rc != SQLITE_OK) + SqliteException? firstException = null; + foreach (var path in GetLoadExtensionPaths(file)) + { + var rc = sqlite3_load_extension(Handle, utf8z.FromString(path), utf8z.FromString(proc), out var errmsg); + if (rc == SQLITE_OK) + { + return; + } + + if (firstException == null) + { + // We store the first exception so that error message looks more obvious if file appears in there + firstException = new SqliteException(Resources.SqliteNativeError(rc, errmsg.utf8_to_string()), rc, rc); + } + } + + if (firstException != null) + { + throw firstException; + } + } + + private static IEnumerable GetLoadExtensionPaths(string file) + { + // we always try original input first + yield return file; + + string? dirName = Path.GetDirectoryName(file); + + // we don't try to guess directories for user, if they pass a path either absolute or relative - they're on their own + if (!string.IsNullOrEmpty(dirName)) + { + yield break; + } + + bool shouldTryAddingLibPrefix = !file.StartsWith("lib", StringComparison.Ordinal) && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + if (shouldTryAddingLibPrefix) + { + yield return $"lib{file}"; + } + + NativeDllSearchDirectories ??= GetNativeDllSearchDirectories(); + + foreach (string dir in NativeDllSearchDirectories) + { + yield return Path.Combine(dir, file); + + if (shouldTryAddingLibPrefix) + { + yield return Path.Combine(dir, $"lib{file}"); + } + } + } + + private static string[] GetNativeDllSearchDirectories() + { + string? searchDirs = AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES") as string; + + if (string.IsNullOrEmpty(searchDirs)) { - throw new SqliteException(Resources.SqliteNativeError(rc, errmsg.utf8_to_string()), rc, rc); + return Array.Empty(); } + + return searchDirs!.Split([ Path.PathSeparator ], StringSplitOptions.RemoveEmptyEntries); } /// From 664644c2b3551a1f4cdd8dafba3d11dc28ac4a9f Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Tue, 25 Feb 2025 10:25:26 +0100 Subject: [PATCH 2/2] Use [] instead of Array.Empty --- src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs index aa293c0a991..88efdbeb07f 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs @@ -687,7 +687,7 @@ private static string[] GetNativeDllSearchDirectories() if (string.IsNullOrEmpty(searchDirs)) { - return Array.Empty(); + return []; } return searchDirs!.Split([ Path.PathSeparator ], StringSplitOptions.RemoveEmptyEntries);