File: Storage\SQLite\v2\SQLitePersistentStorageService.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Composition;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Storage;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.SQLite.v2
{
    internal sealed class SQLitePersistentStorageService : AbstractPersistentStorageService, IWorkspaceService
    {
        [ExportWorkspaceServiceFactory(typeof(SQLitePersistentStorageService)), Shared]
        internal sealed class ServiceFactory : IWorkspaceServiceFactory
        {
            private readonly SQLiteConnectionPoolService _connectionPoolService;
            private readonly IAsynchronousOperationListener _asyncListener;
 
            [ImportingConstructor]
            [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
            public ServiceFactory(
                SQLiteConnectionPoolService connectionPoolService,
                IAsynchronousOperationListenerProvider asyncOperationListenerProvider)
            {
                _connectionPoolService = connectionPoolService;
                _asyncListener = asyncOperationListenerProvider.GetListener(FeatureAttribute.PersistentStorage);
            }
 
            public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
                => new SQLitePersistentStorageService(_connectionPoolService, workspaceServices.GetRequiredService<IPersistentStorageConfiguration>(), _asyncListener);
        }
 
        private const string StorageExtension = "sqlite3";
        private const string PersistentStorageFileName = "storage.ide";
 
        private static bool TryInitializeLibraries() => s_initialized.Value;
 
        private static readonly Lazy<bool> s_initialized = new(TryInitializeLibrariesLazy);
 
        private static bool TryInitializeLibrariesLazy()
        {
            try
            {
                // Necessary to initialize SQLitePCL.
                SQLitePCL.Batteries_V2.Init();
            }
            catch (Exception e) when (e is DllNotFoundException or EntryPointNotFoundException)
            {
                StorageDatabaseLogger.LogException(e);
 
                // In debug also insta fail here.  That way if there is an issue with sqlite (for example with authoring,
                // or with some particular configuration) that get CI coverage that reveals this.
                Debug.Fail("Sqlite failed to load: " + e);
                return false;
            }
 
            return true;
        }
 
        private readonly SQLiteConnectionPoolService _connectionPoolService;
        private readonly IAsynchronousOperationListener _asyncListener;
        private readonly IPersistentStorageFaultInjector? _faultInjector;
 
        public SQLitePersistentStorageService(
            SQLiteConnectionPoolService connectionPoolService,
            IPersistentStorageConfiguration configuration,
            IAsynchronousOperationListener asyncListener)
            : base(configuration)
        {
            _connectionPoolService = connectionPoolService;
            _asyncListener = asyncListener;
        }
 
        public SQLitePersistentStorageService(
            SQLiteConnectionPoolService connectionPoolService,
            IPersistentStorageConfiguration configuration,
            IAsynchronousOperationListener asyncListener,
            IPersistentStorageFaultInjector? faultInjector)
            : this(connectionPoolService, configuration, asyncListener)
        {
            _faultInjector = faultInjector;
        }
 
        protected override string GetDatabaseFilePath(string workingFolderPath)
        {
            Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(workingFolderPath));
            return Path.Combine(workingFolderPath, StorageExtension, nameof(v2), PersistentStorageFileName);
        }
 
        protected override ValueTask<IChecksummedPersistentStorage?> TryOpenDatabaseAsync(
            SolutionKey solutionKey, string workingFolderPath, string databaseFilePath, CancellationToken cancellationToken)
        {
            if (!TryInitializeLibraries())
            {
                // SQLite is not supported on the current platform
                return new((IChecksummedPersistentStorage?)null);
            }
 
            if (solutionKey.FilePath == null)
                return new(NoOpPersistentStorage.GetOrThrow(Configuration.ThrowOnFailure));
 
            return new(SQLitePersistentStorage.TryCreate(
                _connectionPoolService,
                solutionKey,
                workingFolderPath,
                databaseFilePath,
                _asyncListener,
                _faultInjector));
        }
 
        // Error occurred when trying to open this DB.  Try to remove it so we can create a good DB.
        protected override bool ShouldDeleteDatabase(Exception exception) => true;
    }
}