Skip to content

Commit 5b92724

Browse files
Copilotaaronpowell
andauthored
Remove support for extension loading in SQLite integrations (#735)
* Initial plan for issue * Remove SQLite extension loading support from hosting and client integrations Co-authored-by: aaronpowell <[email protected]> * Remove CTASPIRE002 diagnostic from documentation as SQLite extension support is removed Co-authored-by: aaronpowell <[email protected]> * Make CreateConnection return explicit instead of using local variable Co-authored-by: aaronpowell <[email protected]> * Finishing the SQLite integration changes with some test fixes --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: aaronpowell <[email protected]> Co-authored-by: Aaron Powell <[email protected]>
1 parent 90e07ac commit 5b92724

File tree

16 files changed

+12
-432
lines changed

16 files changed

+12
-432
lines changed

docs/diagnostics.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ In these cases, refer to the `<remarks>` docs section of the API for more inform
1010

1111
Once a release of .NET Aspire with that API is available, the API in the .NET Aspire Community Toolkit will be marked as obsolete and will be removed in a future release.
1212

13-
## CTASPIRE002
14-
15-
Support for loading extensions into SQLite requires either a NuGet package or folder path to the library to be provided, and as a result there is some custom logic to load the extension based on the path or NuGet package. This logic will require some experimenting to figure out edge cases, so the feature for extension loading will be kept as experimental until it is proven to be stable.
16-
1713
## CTASPIRE003
1814

1915
The API is marked for deprecation and will be removed in a future release.

src/CommunityToolkit.Aspire.Hosting.Sqlite/CommunityToolkit.Aspire.Hosting.Sqlite.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,4 @@
1111
<ItemGroup>
1212
<InternalsVisibleTo Include="CommunityToolkit.Aspire.Hosting.Sqlite.Tests"></InternalsVisibleTo>
1313
</ItemGroup>
14-
15-
<ItemGroup>
16-
<Compile Include="$(SharedDir)\Sqlite\SqliteExtensionMetadata.cs" Link="Utils\Sqlite\SqliteExtensionMetadata.cs" />
17-
</ItemGroup>
1814
</Project>
Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
using Microsoft.Extensions.Hosting;
2-
using System.Text.Json;
3-
41
namespace Aspire.Hosting.ApplicationModel;
52

63
/// <summary>
@@ -18,18 +15,6 @@ public class SqliteResource(string name, string databasePath, string databaseFil
1815
internal string DatabaseFilePath => Path.Combine(DatabasePath, DatabaseFileName);
1916

2017
/// <inheritdoc/>
21-
public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create($"Data Source={DatabaseFilePath};Cache=Shared;Mode=ReadWriteCreate;Extensions={JsonSerializer.Serialize(Extensions)}");
22-
23-
private readonly List<SqliteExtensionMetadata> extensions = [];
24-
25-
/// <summary>
26-
/// Gets the extensions to be loaded into the database.
27-
/// </summary>
28-
/// <remarks>
29-
/// Extensions are not loaded by the hosting integration, the information is provided for the client to load the extensions.
30-
/// </remarks>
31-
public IReadOnlyCollection<SqliteExtensionMetadata> Extensions => extensions;
32-
33-
internal void AddExtension(SqliteExtensionMetadata extension) => extensions.Add(extension);
18+
public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create($"Data Source={DatabaseFilePath};Cache=Shared;Mode=ReadWriteCreate");
3419
}
3520

src/CommunityToolkit.Aspire.Hosting.Sqlite/SqliteResourceBuilderExtensions.cs

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Aspire.Hosting.ApplicationModel;
2-
using System.Diagnostics.CodeAnalysis;
32

43
namespace Aspire.Hosting;
54

@@ -88,50 +87,5 @@ public static IResourceBuilder<SqliteResource> WithSqliteWeb(this IResourceBuild
8887
return builder;
8988
}
9089

91-
/// <summary>
92-
/// Adds an extension to the Sqlite resource that will be loaded from a NuGet package.
93-
/// </summary>
94-
/// <param name="builder">The resource builder.</param>
95-
/// <param name="extension">The name of the extension file with to add, eg: vec0, without file extension.</param>
96-
/// <param name="packageName">The name of the NuGet package. If this is set to null, the value of <paramref name="extension"/> is used.</param>
97-
/// <returns>The resource builder.</returns>
98-
/// <remarks>
99-
/// Extensions are not loaded by the hosting integration, the information is provided for the client to load the extensions.
100-
///
101-
/// This extension is experimental while the final design of extension loading is decided.
102-
/// </remarks>
103-
[Experimental("CTASPIRE002", UrlFormat = "https://aka.ms/communitytoolkit/aspire/diagnostics#{0}")]
104-
public static IResourceBuilder<SqliteResource> WithNuGetExtension(this IResourceBuilder<SqliteResource> builder, string extension, string? packageName = null)
105-
{
106-
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
107-
ArgumentException.ThrowIfNullOrEmpty(extension, nameof(extension));
108-
109-
builder.Resource.AddExtension(new(extension, packageName ?? extension, IsNuGetPackage: true, ExtensionFolder: null));
110-
111-
return builder;
112-
}
11390

114-
/// <summary>
115-
/// Adds an extension to the Sqlite resource that will be loaded from a local path.
116-
/// </summary>
117-
/// <param name="builder">The resource builder.</param>
118-
/// <param name="extension">The name of the extension file with to add, eg: vec0, without file extension.</param>
119-
/// <param name="extensionPath">The path to the extension file.</param>
120-
/// <returns>The resource builder.</returns>
121-
/// <remarks>
122-
/// Extensions are not loaded by the hosting integration, the information is provided for the client to load the extensions.
123-
///
124-
/// This extension is experimental while the final design of extension loading is decided.
125-
/// </remarks>
126-
[Experimental("CTASPIRE002", UrlFormat = "https://aka.ms/communitytoolkit/aspire/diagnostics#{0}")]
127-
public static IResourceBuilder<SqliteResource> WithLocalExtension(this IResourceBuilder<SqliteResource> builder, string extension, string extensionPath)
128-
{
129-
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
130-
ArgumentException.ThrowIfNullOrEmpty(extension, nameof(extension));
131-
ArgumentException.ThrowIfNullOrEmpty(extensionPath, nameof(extensionPath));
132-
133-
builder.Resource.AddExtension(new(extension, PackageName: null, IsNuGetPackage: false, extensionPath));
134-
135-
return builder;
136-
}
13791
}

src/CommunityToolkit.Aspire.Hosting.Sqlite/api/CommunityToolkit.Aspire.Hosting.Sqlite.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,6 @@ public static partial class SqliteResourceBuilderExtensions
1212
{
1313
public static ApplicationModel.IResourceBuilder<ApplicationModel.SqliteResource> AddSqlite(this IDistributedApplicationBuilder builder, string name, string? databasePath = null, string? databaseFileName = null) { throw null; }
1414

15-
[System.Diagnostics.CodeAnalysis.Experimental("CTASPIRE002", UrlFormat = "https://aka.ms/communitytoolkit/aspire/diagnostics#{0}")]
16-
public static ApplicationModel.IResourceBuilder<ApplicationModel.SqliteResource> WithLocalExtension(this ApplicationModel.IResourceBuilder<ApplicationModel.SqliteResource> builder, string extension, string extensionPath) { throw null; }
17-
18-
[System.Diagnostics.CodeAnalysis.Experimental("CTASPIRE002", UrlFormat = "https://aka.ms/communitytoolkit/aspire/diagnostics#{0}")]
19-
public static ApplicationModel.IResourceBuilder<ApplicationModel.SqliteResource> WithNuGetExtension(this ApplicationModel.IResourceBuilder<ApplicationModel.SqliteResource> builder, string extension, string? packageName = null) { throw null; }
20-
2115
public static ApplicationModel.IResourceBuilder<ApplicationModel.SqliteResource> WithSqliteWeb(this ApplicationModel.IResourceBuilder<ApplicationModel.SqliteResource> builder, System.Action<ApplicationModel.IResourceBuilder<ApplicationModel.SqliteWebResource>>? configureContainer = null, string? containerName = null) { throw null; }
2216
}
2317
}
@@ -29,8 +23,6 @@ public partial class SqliteResource : Resource, IResourceWithConnectionString, I
2923
public SqliteResource(string name, string databasePath, string databaseFileName) : base(default!) { }
3024

3125
public ReferenceExpression ConnectionStringExpression { get { throw null; } }
32-
33-
public System.Collections.Generic.IReadOnlyCollection<Microsoft.Extensions.Hosting.SqliteExtensionMetadata> Extensions { get { throw null; } }
3426
}
3527

3628
public partial class SqliteWebResource : ContainerResource, IResourceWithConnectionString, IResource, IManifestExpressionProvider, IValueProvider, IValueWithReferences
@@ -41,11 +33,4 @@ public SqliteWebResource(string name) : base(default!, default) { }
4133

4234
public EndpointReference PrimaryEndpoint { get { throw null; } }
4335
}
44-
}
45-
46-
namespace Microsoft.Extensions.Hosting
47-
{
48-
public partial record SqliteExtensionMetadata(string Extension, string? PackageName, bool IsNuGetPackage, string? ExtensionFolder)
49-
{
50-
}
5136
}

src/CommunityToolkit.Aspire.Microsoft.Data.Sqlite/AspireSqliteExtensions.cs

Lines changed: 1 addition & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,8 @@
33
using Microsoft.Data.Sqlite;
44
using Microsoft.Extensions.Configuration;
55
using Microsoft.Extensions.DependencyInjection;
6-
using Microsoft.Extensions.DependencyModel;
76
using Microsoft.Extensions.Diagnostics.HealthChecks;
8-
using Microsoft.Extensions.Logging;
97
using System.Data.Common;
10-
using System.Runtime.InteropServices;
11-
using System.Text.Json;
12-
using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
138

149
namespace Microsoft.Extensions.Hosting;
1510

@@ -67,15 +62,6 @@ private static void AddSqliteClient(
6762
settings.ConnectionString = connectionString;
6863
}
6964

70-
if (!string.IsNullOrEmpty(settings.ConnectionString))
71-
{
72-
var cbs = new DbConnectionStringBuilder { ConnectionString = settings.ConnectionString };
73-
if (cbs.TryGetValue("Extensions", out var extensions))
74-
{
75-
settings.Extensions = JsonSerializer.Deserialize<IEnumerable<SqliteExtensionMetadata>>((string)extensions) ?? [];
76-
}
77-
}
78-
7965
configureSettings?.Invoke(settings);
8066

8167
builder.RegisterSqliteServices(settings, connectionName, serviceKey);
@@ -115,181 +101,8 @@ private static void RegisterSqliteServices(
115101

116102
SqliteConnection CreateConnection(IServiceProvider sp, object? key)
117103
{
118-
var logger = sp.GetRequiredService<ILogger<SqliteConnection>>();
119104
ConnectionStringValidation.ValidateConnectionString(settings.ConnectionString, connectionName, DefaultConfigSectionName);
120-
var csb = new DbConnectionStringBuilder { ConnectionString = settings.ConnectionString };
121-
if (csb.ContainsKey("Extensions"))
122-
{
123-
csb.Remove("Extensions");
124-
}
125-
var connection = new SqliteConnection(csb.ConnectionString);
126-
127-
foreach (var extension in settings.Extensions)
128-
{
129-
if (extension.IsNuGetPackage)
130-
{
131-
if (string.IsNullOrEmpty(extension.PackageName))
132-
{
133-
throw new InvalidOperationException("PackageName is required when loading an extension from a NuGet package.");
134-
}
135-
136-
EnsureLoadableFromNuGet(extension.Extension, extension.PackageName, logger);
137-
}
138-
else
139-
{
140-
if (string.IsNullOrEmpty(extension.ExtensionFolder))
141-
{
142-
throw new InvalidOperationException("ExtensionFolder is required when loading an extension from a folder.");
143-
}
144-
145-
EnsureLoadableFromLocalPath(extension.Extension, extension.ExtensionFolder);
146-
}
147-
connection.LoadExtension(extension.Extension);
148-
}
149-
150-
return connection;
151-
}
152-
}
153-
154-
// Adapted from https://github.com/dotnet/docs/blob/dbbeda13bf016a6ff76b0baab1488c927a64ff24/samples/snippets/standard/data/sqlite/ExtensionsSample/Program.cs#L40
155-
internal static void EnsureLoadableFromNuGet(string package, string library, ILogger<SqliteConnection> logger)
156-
{
157-
var runtimeLibrary = DependencyContext.Default?.RuntimeLibraries.FirstOrDefault(l => l.Name == package);
158-
if (runtimeLibrary is null)
159-
{
160-
logger.LogInformation("Could not find the runtime library for package {Package}", package);
161-
return;
162-
}
163-
164-
string sharedLibraryExtension;
165-
string pathVariableName = "PATH";
166-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
167-
{
168-
sharedLibraryExtension = ".dll";
169-
}
170-
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
171-
{
172-
sharedLibraryExtension = ".so";
173-
pathVariableName = "LD_LIBRARY_PATH";
174-
}
175-
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
176-
{
177-
sharedLibraryExtension = ".dylib";
178-
pathVariableName = "DYLD_LIBRARY_PATH";
179-
}
180-
else
181-
{
182-
throw new NotSupportedException("Unsupported OS platform");
183-
}
184-
185-
var candidateAssets = new Dictionary<(string? Package, string Asset), int>();
186-
var rid = RuntimeEnvironment.GetRuntimeIdentifier();
187-
var rids = DependencyContext.Default?.RuntimeGraph.First(g => g.Runtime == rid).Fallbacks.ToList() ?? [];
188-
rids.Insert(0, rid);
189-
190-
logger.LogInformation("Looking for {Library} in {Package} runtime assets", library, package);
191-
logger.LogInformation("Possible runtime identifiers: {Rids}", string.Join(", ", rids));
192-
193-
foreach (var group in runtimeLibrary.NativeLibraryGroups)
194-
{
195-
foreach (var file in group.RuntimeFiles)
196-
{
197-
if (string.Equals(
198-
Path.GetFileName(file.Path),
199-
library + sharedLibraryExtension,
200-
StringComparison.OrdinalIgnoreCase))
201-
{
202-
var fallbacks = rids.IndexOf(group.Runtime);
203-
if (fallbacks != -1)
204-
{
205-
logger.LogInformation("Found {Library} in {Package} runtime assets at {Path}", library, package, file.Path);
206-
candidateAssets.Add((runtimeLibrary.Path, file.Path), fallbacks);
207-
}
208-
}
209-
}
210-
}
211-
212-
var assetPath = candidateAssets
213-
.OrderBy(p => p.Value)
214-
.Select(p => p.Key)
215-
.FirstOrDefault();
216-
if (assetPath != default)
217-
{
218-
string? assetDirectory = null;
219-
if (File.Exists(Path.Combine(AppContext.BaseDirectory, assetPath.Asset)))
220-
{
221-
// NB: Framework-dependent deployments copy assets to the application base directory
222-
assetDirectory = Path.Combine(
223-
AppContext.BaseDirectory,
224-
Path.GetDirectoryName(assetPath.Asset.Replace('/', Path.DirectorySeparatorChar))!);
225-
226-
logger.LogInformation("Found {Library} in {Package} runtime assets at {Path}", library, package, assetPath.Asset);
227-
}
228-
else
229-
{
230-
string? assetFullPath = null;
231-
var probingDirectories = ((string?)AppDomain.CurrentDomain.GetData("PROBING_DIRECTORIES"))?
232-
.Split(Path.PathSeparator) ?? [];
233-
foreach (var directory in probingDirectories)
234-
{
235-
var candidateFullPath = Path.Combine(
236-
directory,
237-
assetPath.Package ?? "",
238-
assetPath.Asset);
239-
if (File.Exists(candidateFullPath))
240-
{
241-
assetFullPath = candidateFullPath;
242-
}
243-
}
244-
245-
assetDirectory = Path.GetDirectoryName(assetFullPath);
246-
logger.LogInformation("Found {Library} in {Package} runtime assets at {Path} (using PROBING_DIRECTORIES: {ProbingDirectories})", library, package, assetFullPath, string.Join(",", probingDirectories));
247-
}
248-
249-
var path = new HashSet<string>(Environment.GetEnvironmentVariable(pathVariableName)!.Split(Path.PathSeparator));
250-
251-
if (assetDirectory is not null && path.Add(assetDirectory))
252-
{
253-
logger.LogInformation("Adding {AssetDirectory} to {PathVariableName}", assetDirectory, pathVariableName);
254-
Environment.SetEnvironmentVariable(pathVariableName, string.Join(Path.PathSeparator, path));
255-
logger.LogInformation("Set {PathVariableName} to: {PathVariableValue}", pathVariableName, Environment.GetEnvironmentVariable(pathVariableName));
256-
}
257-
}
258-
else
259-
{
260-
logger.LogInformation("Could not find {Library} in {Package} runtime assets", library, package);
261-
}
262-
}
263-
264-
internal static void EnsureLoadableFromLocalPath(string library, string assetDirectory)
265-
{
266-
string sharedLibraryExtension;
267-
string pathVariableName = "PATH";
268-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
269-
{
270-
sharedLibraryExtension = ".dll";
271-
}
272-
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
273-
{
274-
sharedLibraryExtension = ".so";
275-
pathVariableName = "LD_LIBRARY_PATH";
276-
}
277-
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
278-
{
279-
sharedLibraryExtension = ".dylib";
280-
pathVariableName = "DYLD_LIBRARY_PATH";
281-
}
282-
else
283-
{
284-
throw new NotSupportedException("Unsupported OS platform");
285-
}
286-
287-
if (File.Exists(Path.Combine(assetDirectory, library + sharedLibraryExtension)))
288-
{
289-
var path = new HashSet<string>(Environment.GetEnvironmentVariable(pathVariableName)!.Split(Path.PathSeparator));
290-
291-
if (assetDirectory is not null && path.Add(assetDirectory))
292-
Environment.SetEnvironmentVariable(pathVariableName, string.Join(Path.PathSeparator, path));
105+
return new SqliteConnection(settings.ConnectionString);
293106
}
294107
}
295108
}

src/CommunityToolkit.Aspire.Microsoft.Data.Sqlite/CommunityToolkit.Aspire.Microsoft.Data.Sqlite.csproj

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
<ItemGroup>
99
<PackageReference Include="AspNetCore.HealthChecks.Sqlite" />
1010
<PackageReference Include="Microsoft.Data.Sqlite" />
11-
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" />
12-
<PackageReference Include="Microsoft.Extensions.DependencyModel" />
1311
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
1412
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
1513
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
@@ -18,7 +16,6 @@
1816
<ItemGroup>
1917
<Compile Include="$(SharedDir)\HealthChecksExtensions.cs" Link="Utils\HealthChecksExtensions.cs" />
2018
<Compile Include="$(SharedDir)\ConnectionStringValidation.cs" Link="Utils\ConnectionStringValidation.cs" />
21-
<Compile Include="$(SharedDir)\Sqlite\SqliteExtensionMetadata.cs" Link="Utils\Sqlite\SqliteExtensionMetadata.cs" />
2219
</ItemGroup>
2320

2421
</Project>

src/CommunityToolkit.Aspire.Microsoft.Data.Sqlite/SqliteConnectionSettings.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,4 @@ public sealed class SqliteConnectionSettings
1717
/// The default value is <see langword="false"/>.
1818
/// </value>
1919
public bool DisableHealthChecks { get; set; }
20-
21-
/// <summary>
22-
/// Extensions to be loaded into the database.
23-
/// </summary>
24-
public IEnumerable<SqliteExtensionMetadata> Extensions { get; set; } = [];
2520
}

src/CommunityToolkit.Aspire.Microsoft.Data.Sqlite/api/CommunityToolkit.Aspire.Microsoft.Data.Sqlite.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,5 @@ public sealed partial class SqliteConnectionSettings
2020
public string? ConnectionString { get { throw null; } set { } }
2121

2222
public bool DisableHealthChecks { get { throw null; } set { } }
23-
24-
public System.Collections.Generic.IEnumerable<SqliteExtensionMetadata> Extensions { get { throw null; } set { } }
25-
}
26-
27-
public partial record SqliteExtensionMetadata(string Extension, string? PackageName, bool IsNuGetPackage, string? ExtensionFolder)
28-
{
2923
}
3024
}

0 commit comments

Comments
 (0)