From e52e0fb934c1cec902473c80fde63138df9aff42 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 4 Mar 2025 02:08:39 +0100 Subject: [PATCH 1/2] Improve LoadExtension to work correctly with dotnet run and lib* named libs (#35718) * Improve LoadExtension to work correctly with dotnet run and lib packages * Use [] instead of Array.Empty (cherry picked from commit b1d34dc9a22c239fac99e893fde36e74bc546c05) Co-authored-by: Krzysztof Wicher --- .github/workflows/TestCosmos.yaml | 2 +- .../SqliteConnection.cs | 82 ++++++++++++++++++- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/.github/workflows/TestCosmos.yaml b/.github/workflows/TestCosmos.yaml index 52b8aeb3cbd..cb9d7103301 100644 --- a/.github/workflows/TestCosmos.yaml +++ b/.github/workflows/TestCosmos.yaml @@ -35,7 +35,7 @@ jobs: shell: cmd - name: Publish Test Results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: test-results diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs index 02e75c9aa25..0c2135a68f4 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs @@ -23,6 +23,9 @@ namespace Microsoft.Data.Sqlite /// Async Limitations public partial class SqliteConnection : DbConnection { + private static readonly bool UseOldBehavior35715 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35715", out var enabled35715) && enabled35715; + internal const string MainDatabaseName = "main"; private const int SQLITE_WIN32_DATA_DIRECTORY_TYPE = 1; @@ -48,6 +51,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") @@ -626,11 +631,82 @@ 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) + if (UseOldBehavior35715) + { + var rc = sqlite3_load_extension(Handle, utf8z.FromString(file), utf8z.FromString(proc), out var errmsg); + if (rc != SQLITE_OK) + { + throw new SqliteException(Resources.SqliteNativeError(rc, errmsg.utf8_to_string()), rc, rc); + } + } + else + { + 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)) { - throw new SqliteException(Resources.SqliteNativeError(rc, errmsg.utf8_to_string()), rc, rc); + 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)) + { + return Array.Empty(); + } + + return searchDirs!.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); } /// From eeab91811da4b84107cb69bc04ba02ebb8f42c7d Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 4 Mar 2025 09:34:12 +0100 Subject: [PATCH 2/2] Transform Span-based overloads to Enumerable in funcletizer (#35720) Fixes #35100 (cherry picked from commit 6c0106b2693c3d3d87c26d8d63d97ad2236c3f4f) --- .../ParameterExtractingExpressionVisitor.cs | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs index 558104b7d40..410f2dd2839 100644 --- a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs @@ -31,6 +31,9 @@ public class ParameterExtractingExpressionVisitor : ExpressionVisitor private static readonly bool UseOldBehavior31552 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31552", out var enabled31552) && enabled31552; + private static readonly bool UseOldBehavior35100 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -181,9 +184,11 @@ protected override Expression VisitConditional(ConditionalExpression conditional /// protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { + var method = methodCallExpression.Method; + if (!UseOldBehavior31552 - && methodCallExpression.Method.DeclaringType == typeof(EF) - && methodCallExpression.Method.Name == nameof(EF.Constant)) + && method.DeclaringType == typeof(EF) + && method.Name == nameof(EF.Constant)) { // If this is a call to EF.Constant(), then examine its operand. If the operand isn't evaluatable (i.e. contains a reference // to a database table), throw immediately. @@ -197,6 +202,52 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp return Evaluate(operand, generateParameter: false); } + // .NET 10 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans"). + // Unfortunately, the LINQ interpreter does not support ref structs, so we rewrite e.g. MemoryExtensions.Contains to + // Enumerable.Contains here. See https://github.com/dotnet/runtime/issues/109757. + if (method.DeclaringType == typeof(MemoryExtensions) && !UseOldBehavior35100) + { + switch (method.Name) + { + case nameof(MemoryExtensions.Contains) + when methodCallExpression.Arguments is [var arg0, var arg1] && + TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0): + { + return Visit( + Expression.Call( + EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]), + unwrappedArg0, arg1)); + } + + case nameof(MemoryExtensions.SequenceEqual) + when methodCallExpression.Arguments is [var arg0, var arg1] + && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0) + && TryUnwrapSpanImplicitCast(arg1, out var unwrappedArg1): + return Visit( + Expression.Call( + EnumerableMethods.SequenceEqual.MakeGenericMethod(method.GetGenericArguments()[0]), + unwrappedArg0, unwrappedArg1)); + } + + static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result) + { + if (expression is MethodCallExpression + { + Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType }, + Arguments: [var unwrapped] + } + && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition + && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>))) + { + result = unwrapped; + return true; + } + + result = null; + return false; + } + } + return base.VisitMethodCall(methodCallExpression); }