diff --git a/src/DryDB/ReadOnlyDatabase.cs b/src/DryDB/ReadOnlyDatabase.cs index b596ebc..ff55d52 100644 --- a/src/DryDB/ReadOnlyDatabase.cs +++ b/src/DryDB/ReadOnlyDatabase.cs @@ -15,19 +15,21 @@ public record DatabaseLoadOptions { public static DatabaseLoadOptions Default => new(); + // This factory must not reference any file I/O type: it is rooted by every use + // of DatabaseLoadOptions, so a FileStream branch here would link file support + // (pread and friends) into applications that never open a file — see + // FileStorage.Factory, which OpenFileAsync swaps in, for the file path. public static readonly StorageFactory DefaultStorageFactory = (stream, pageSize) => { - if (stream is FileStream fs) - { - return new PreadPageLoader(fs.SafeFileHandle); - } - if (stream is MemoryStream ms) { return new InMemoryPageLoader(ms.ToArray()); } - throw new NotSupportedException($"unsupported stream type: {stream.GetType().Name}"); + throw new NotSupportedException( + $"unsupported stream type: {stream.GetType().Name}. " + + $"To read a file, use ReadOnlyDatabase.OpenFileAsync, or set " + + $"DatabaseLoadOptions.StorageFactory = FileStorage.Factory."); }; public int CacheSize { get; set; } = 2000 * 1024 * 1024; @@ -39,6 +41,10 @@ public sealed class ReadOnlyDatabase : IDisposable public static async ValueTask OpenFileAsync(string path, DatabaseLoadOptions? options = null, CancellationToken cancellationToken = default) { options ??= DatabaseLoadOptions.Default; + if (ReferenceEquals(options.StorageFactory, DatabaseLoadOptions.DefaultStorageFactory)) + { + options = options with { StorageFactory = FileStorage.Factory }; + } var fs = File.OpenRead(path); return await OpenAsync(fs, options, cancellationToken); } diff --git a/src/DryDB/Storages/FileStorage.cs b/src/DryDB/Storages/FileStorage.cs new file mode 100644 index 0000000..2009955 --- /dev/null +++ b/src/DryDB/Storages/FileStorage.cs @@ -0,0 +1,21 @@ +using System.IO; + +namespace DryDB.Storages; + +/// +/// The that serves s through +/// . Kept out of +/// so that applications that +/// never read a file do not link file I/O (trimmers and AOT compilers root everything +/// the default factory references); +/// swaps this factory in. To pass an already opened to +/// , set +/// to . +/// +public static class FileStorage +{ + public static readonly StorageFactory Factory = (stream, pageSize) => + stream is FileStream fs + ? new PreadPageLoader(fs.SafeFileHandle) + : DatabaseLoadOptions.DefaultStorageFactory(stream, pageSize); +}