提交 7c689f4b 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #21182 from CyrusNajmabadi/removeEsent

Remove Esent entirely from Roslyn.
......@@ -10,7 +10,6 @@
<FakeSignVersion>0.9.2</FakeSignVersion>
<GitLinkVersion>3.0.0-unstable0090</GitLinkVersion>
<LibGit2SharpVersion>0.22.0</LibGit2SharpVersion>
<ManagedEsentVersion>1.9.4</ManagedEsentVersion>
<MicroBuildCoreVersion>0.2.0</MicroBuildCoreVersion>
<MicroBuildCoreSentinelVersion>1.0.0</MicroBuildCoreSentinelVersion>
<MicroBuildPluginsSwixBuildVersion>1.1.0-g0701ee829f</MicroBuildPluginsSwixBuildVersion>
......
......@@ -27,6 +27,5 @@
<PackageReference Include="RoslynTools.ReferenceAssemblies" Version="$(RoslynToolsReferenceAssembliesVersion)" />
<PackageReference Include="GitLink" Version="$(GitLinkVersion)" />
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="$(MicrosoftVSSDKBuildToolsVersion)" />
<PackageReference Include="ManagedEsent" Version="$(ManagedEsentVersion)" />
</ItemGroup>
</Project>
......@@ -108,7 +108,6 @@
],
"exclude": [
"Esent.Interop.dll",
"Microsoft.CodeAnalysis.Elfie.dll",
"Microsoft.DiaSymReader.dll",
"Microsoft.DiaSymReader.Native.amd64.dll",
......
......@@ -21,7 +21,6 @@
<dependency id="System.Threading.Tasks.Parallel" version="$SystemThreadingTasksParallelVersion$" />
</group>
<group targetFramework="net46">
<dependency id="ManagedEsent" version="$ManagedEsentVersion$" />
<dependency id="Microsoft.CodeAnalysis.Common" version="[$version$]" />
<dependency id="System.Composition" version="$SystemCompositionVersion$" />
</group>
......
......@@ -158,7 +158,6 @@ Public Class BuildDevDivInsertionFiles
Private ReadOnly IntegrationTestFiles As String() = {
"xunit.*.dll",
"*.UnitTests.dll.config",
"Esent.Interop.dll",
"InteractiveHost.exe",
"Microsoft.CodeAnalysis.CSharp.dll",
"Microsoft.CodeAnalysis.CSharp.EditorFeatures.dll",
......@@ -293,7 +292,6 @@ Public Class BuildDevDivInsertionFiles
"xunit.*.dll",
"PerfTests",
"BasicUndo.dll",
"Esent.Interop.dll",
"InteractiveHost.exe",
"Microsoft.CodeAnalysis.CSharp.dll",
"Microsoft.CodeAnalysis.CSharp.EditorFeatures.dll",
......@@ -862,7 +860,6 @@ Public Class BuildDevDivInsertionFiles
add("Dlls\TestUtilities.Desktop\Roslyn.Test.Utilities.Desktop.dll")
add("Dlls\TestUtilities\Roslyn.Test.Utilities.dll")
add("UnitTests\EditorServicesTest\BasicUndo.dll")
add("UnitTests\EditorServicesTest\Esent.Interop.dll")
add("UnitTests\EditorServicesTest\Moq.dll")
add("UnitTests\EditorServicesTest\Microsoft.CodeAnalysis.Test.Resources.Proprietary.dll")
add("UnitTests\EditorServicesTest\Microsoft.DiaSymReader.PortablePdb.dll")
......
......@@ -37,9 +37,6 @@
<PackageReference Include="System.Reflection.Metadata">
<Version>$(SystemReflectionMetadataVersion)</Version>
</PackageReference>
<PackageReference Include="ManagedEsent">
<Version>$(ManagedEsentVersion)</Version>
</PackageReference>
<PackageReference Include="System.AppContext">
<Version>$(SystemAppContextVersion)</Version>
</PackageReference>
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.Esent;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices
{
/// <remarks>
/// Tests are inherited from <see cref="AbstractPersistentStorageTests"/>. That way we can
/// write tests once and have them run against all <see cref="IPersistentStorageService"/>
/// implementations.
/// </remarks>
public class EsentPersistentStorageTests : AbstractPersistentStorageTests
{
internal override IPersistentStorageService GetStorageService(IPersistentStorageFaultInjector faultInjector)
=> new EsentPersistentStorageService(_persistentEnabledOptionService, testing: true);
}
}
......@@ -14,7 +14,6 @@
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Storage;
......@@ -26,14 +25,11 @@
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.TextManager.Interop;
using Roslyn.Utilities;
using Roslyn.VisualStudio.ProjectSystem;
using VSLangProj;
using VSLangProj140;
using OLEServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
using OleInterop = Microsoft.VisualStudio.OLE.Interop;
using Microsoft.CodeAnalysis.Esent;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
......
......@@ -23,7 +23,6 @@
[assembly: ProvideRoslynBindingRedirection("Microsoft.VisualStudio.LanguageServices.CSharp.dll")]
[assembly: ProvideRoslynBindingRedirection("Microsoft.VisualStudio.LanguageServices.SolutionExplorer.dll")]
[assembly: ProvideRoslynBindingRedirection("Esent.Interop.dll")]
[assembly: ProvideRoslynBindingRedirection("Microsoft.CodeAnalysis.Elfie.dll")]
[assembly: ProvideCodeBase(CodeBase = @"$PackageFolder$\SQLitePCLRaw.batteries_green.DLL")]
......
......@@ -184,7 +184,6 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<NuGetPackageToIncludeInVsix Include="ManagedEsent" />
<NuGetPackageToIncludeInVsix Include="SQLitePCLRaw.bundle_green" />
<NuGetPackageToIncludeInVsix Include="Microsoft.CodeAnalysis.Elfie" />
<!-- Visual Studio ships with some, but not all, of the assemblies in System.Composition, but we need them all -->
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Storage;
using Microsoft.Isam.Esent;
using Microsoft.Isam.Esent.Interop;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentPersistentStorage : AbstractPersistentStorage
{
// cache delegates so that we don't re-create it every times
private readonly Func<int, object, object, object, CancellationToken, Stream> _readStreamSolution;
private readonly Func<EsentStorage.Key, int, object, object, CancellationToken, Stream> _readStream;
private readonly Func<int, Stream, object, object, CancellationToken, bool> _writeStreamSolution;
private readonly Func<EsentStorage.Key, int, Stream, object, CancellationToken, bool> _writeStream;
private readonly ConcurrentDictionary<string, int> _nameTableCache;
private readonly EsentStorage _esentStorage;
public EsentPersistentStorage(
IOptionService optionService,
string workingFolderPath,
string solutionFilePath,
string databaseFile,
Action<AbstractPersistentStorage> disposer)
: base(optionService, workingFolderPath, solutionFilePath, databaseFile, disposer)
{
// cache delegates
_readStreamSolution = ReadStreamSolution;
_readStream = ReadStream;
_writeStreamSolution = WriteStreamSolution;
_writeStream = WriteStream;
// solution must exist in disk. otherwise, we shouldn't be here at all.
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(solutionFilePath));
_nameTableCache = new ConcurrentDictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var enablePerformanceMonitor = optionService.GetOption(PersistentStorageOptions.EsentPerformanceMonitor);
_esentStorage = new EsentStorage(DatabaseFile, enablePerformanceMonitor);
}
public override void Initialize(Solution solution)
{
_esentStorage.Initialize();
}
public override Task<Stream> ReadStreamAsync(Document document, string name, CancellationToken cancellationToken = default)
{
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(name));
if (!PersistenceEnabled)
{
return SpecializedTasks.Default<Stream>();
}
if (!TryGetProjectAndDocumentKey(document, out var key) ||
!TryGetUniqueNameId(name, out var nameId))
{
return SpecializedTasks.Default<Stream>();
}
var stream = EsentExceptionWrapper(key, nameId, _readStream, cancellationToken);
return SpecializedTasks.DefaultOrResult(stream);
}
public override Task<Stream> ReadStreamAsync(Project project, string name, CancellationToken cancellationToken = default)
{
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(name));
if (!PersistenceEnabled)
{
return SpecializedTasks.Default<Stream>();
}
if (!TryGetProjectKey(project, out var key) ||
!TryGetUniqueNameId(name, out var nameId))
{
return SpecializedTasks.Default<Stream>();
}
var stream = EsentExceptionWrapper(key, nameId, _readStream, cancellationToken);
return SpecializedTasks.DefaultOrResult(stream);
}
public override Task<Stream> ReadStreamAsync(string name, CancellationToken cancellationToken = default)
{
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(name));
if (!PersistenceEnabled)
{
return SpecializedTasks.Default<Stream>();
}
if (!TryGetUniqueNameId(name, out var nameId))
{
return SpecializedTasks.Default<Stream>();
}
var stream = EsentExceptionWrapper(nameId, _readStreamSolution, cancellationToken);
return SpecializedTasks.DefaultOrResult(stream);
}
private Stream ReadStream(EsentStorage.Key key, int nameId, object unused1, object unused2, CancellationToken cancellationToken)
{
using (var accessor = GetAccessor(key))
using (var esentStream = accessor.GetReadStream(key, nameId))
{
if (esentStream == null)
{
return null;
}
// this will copy over esent stream and let it go.
return SerializableBytes.CreateReadableStream(esentStream, cancellationToken);
}
}
private Stream ReadStreamSolution(int nameId, object unused1, object unused2, object unused3, CancellationToken cancellationToken)
{
using (var accessor = _esentStorage.GetSolutionTableAccessor())
using (var esentStream = accessor.GetReadStream(nameId))
{
if (esentStream == null)
{
return null;
}
// this will copy over esent stream and let it go.
return SerializableBytes.CreateReadableStream(esentStream, cancellationToken);
}
}
public override Task<bool> WriteStreamAsync(Document document, string name, Stream stream, CancellationToken cancellationToken = default)
{
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(name));
Contract.ThrowIfNull(stream);
if (!PersistenceEnabled)
{
return SpecializedTasks.False;
}
if (!TryGetProjectAndDocumentKey(document, out var key) ||
!TryGetUniqueNameId(name, out var nameId))
{
return SpecializedTasks.False;
}
var success = EsentExceptionWrapper(key, nameId, stream, _writeStream, cancellationToken);
return success ? SpecializedTasks.True : SpecializedTasks.False;
}
public override Task<bool> WriteStreamAsync(Project project, string name, Stream stream, CancellationToken cancellationToken = default)
{
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(name));
Contract.ThrowIfNull(stream);
if (!PersistenceEnabled)
{
return SpecializedTasks.False;
}
if (!TryGetProjectKey(project, out var key) ||
!TryGetUniqueNameId(name, out var nameId))
{
return SpecializedTasks.False;
}
var success = EsentExceptionWrapper(key, nameId, stream, _writeStream, cancellationToken);
return success ? SpecializedTasks.True : SpecializedTasks.False;
}
public override Task<bool> WriteStreamAsync(string name, Stream stream, CancellationToken cancellationToken = default)
{
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(name));
Contract.ThrowIfNull(stream);
if (!PersistenceEnabled)
{
return SpecializedTasks.False;
}
if (!TryGetUniqueNameId(name, out var nameId))
{
return SpecializedTasks.False;
}
var success = EsentExceptionWrapper(nameId, stream, _writeStreamSolution, cancellationToken);
return success ? SpecializedTasks.True : SpecializedTasks.False;
}
private bool WriteStream(EsentStorage.Key key, int nameId, Stream stream, object unused1, CancellationToken cancellationToken)
{
using (var accessor = GetAccessor(key))
using (var esentStream = accessor.GetWriteStream(key, nameId))
{
WriteToStream(stream, esentStream, cancellationToken);
return accessor.ApplyChanges();
}
}
private bool WriteStreamSolution(int nameId, Stream stream, object unused1, object unused2, CancellationToken cancellationToken)
{
using (var accessor = _esentStorage.GetSolutionTableAccessor())
using (var esentStream = accessor.GetWriteStream(nameId))
{
WriteToStream(stream, esentStream, cancellationToken);
return accessor.ApplyChanges();
}
}
public override void Close()
{
_esentStorage.Close();
}
private bool TryGetUniqueNameId(string name, out int id)
{
return TryGetUniqueId(name, false, out id);
}
private bool TryGetUniqueFileId(string path, out int id)
{
return TryGetUniqueId(path, true, out id);
}
private bool TryGetUniqueId(string value, bool fileCheck, out int id)
{
id = default;
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
// if we already know, get the id
if (_nameTableCache.TryGetValue(value, out id))
{
return true;
}
// we only persist for things that actually exist
if (fileCheck && !File.Exists(value))
{
return false;
}
try
{
var uniqueIdValue = fileCheck ? PathUtilities.GetRelativePath(Path.GetDirectoryName(SolutionFilePath), value) : value;
id = _nameTableCache.GetOrAdd(value, _esentStorage.GetUniqueId(uniqueIdValue));
return true;
}
catch (Exception ex)
{
// if we get fatal errors from esent such as disk out of space or log file corrupted by other process and etc
// don't crash VS, but let VS know it can't use esent. we will gracefully recover issue by using memory.
StorageDatabaseLogger.LogException(ex);
return false;
}
}
private static void WriteToStream(Stream inputStream, Stream esentStream, CancellationToken cancellationToken)
{
var buffer = SharedPools.ByteArray.Allocate();
try
{
int bytesRead;
do
{
cancellationToken.ThrowIfCancellationRequested();
bytesRead = inputStream.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
esentStream.Write(buffer, 0, bytesRead);
}
}
while (bytesRead > 0);
// flush the data and trim column size of necessary
esentStream.Flush();
}
finally
{
SharedPools.ByteArray.Free(buffer);
}
}
private EsentStorage.ProjectDocumentTableAccessor GetAccessor(EsentStorage.Key key)
{
return key.DocumentIdOpt.HasValue ?
_esentStorage.GetDocumentTableAccessor() :
(EsentStorage.ProjectDocumentTableAccessor)_esentStorage.GetProjectTableAccessor();
}
private bool TryGetProjectAndDocumentKey(Document document, out EsentStorage.Key key)
{
key = default;
if (!TryGetProjectId(document.Project, out var projectId, out var projectNameId) ||
!TryGetUniqueFileId(document.FilePath, out var documentId))
{
return false;
}
key = new EsentStorage.Key(projectId, projectNameId, documentId);
return true;
}
private bool TryGetProjectKey(Project project, out EsentStorage.Key key)
{
key = default;
if (!TryGetProjectId(project, out var projectId, out var projectNameId))
{
return false;
}
key = new EsentStorage.Key(projectId, projectNameId);
return true;
}
private bool TryGetProjectId(Project project, out int projectId, out int projectNameId)
{
projectId = default;
projectNameId = default;
return TryGetUniqueFileId(project.FilePath, out projectId) && TryGetUniqueNameId(project.Name, out projectNameId);
}
private TResult EsentExceptionWrapper<TArg1, TResult>(TArg1 arg1, Func<TArg1, object, object, object, CancellationToken, TResult> func, CancellationToken cancellationToken)
{
return EsentExceptionWrapper(arg1, (object)null, func, cancellationToken);
}
private TResult EsentExceptionWrapper<TArg1, TArg2, TResult>(
TArg1 arg1, TArg2 arg2, Func<TArg1, TArg2, object, object, CancellationToken, TResult> func, CancellationToken cancellationToken)
{
return EsentExceptionWrapper(arg1, arg2, (object)null, func, cancellationToken);
}
private TResult EsentExceptionWrapper<TArg1, TArg2, TArg3, TResult>(
TArg1 arg1, TArg2 arg2, TArg3 arg3, Func<TArg1, TArg2, TArg3, object, CancellationToken, TResult> func, CancellationToken cancellationToken)
{
return EsentExceptionWrapper(arg1, arg2, arg3, (object)null, func, cancellationToken);
}
private TResult EsentExceptionWrapper<TArg1, TArg2, TArg3, TArg4, TResult>(
TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, Func<TArg1, TArg2, TArg3, TArg4, CancellationToken, TResult> func, CancellationToken cancellationToken)
{
try
{
return func(arg1, arg2, arg3, arg4, cancellationToken);
}
catch (EsentInvalidSesidException)
{
// operation was in-fly when Esent instance was shutdown - ignore the error
}
catch (OperationCanceledException)
{
}
catch (EsentException ex)
{
if (!_esentStorage.IsClosed)
{
// ignore esent exception if underlying storage was closed
// there is not much we can do here.
// internally we use it as a way to cache information between sessions anyway.
// no functionality will be affected by this except perf
Logger.Log(FunctionId.PersistenceService_WriteAsyncFailed, "Esent Failed : " + ex.Message);
}
}
catch (Exception ex)
{
// ignore exception
// there is not much we can do here.
// internally we use it as a way to cache information between sessions anyway.
// no functionality will be affected by this except perf
Logger.Log(FunctionId.PersistenceService_WriteAsyncFailed, "Failed : " + ex.Message);
}
return default;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.SolutionSize;
using Microsoft.CodeAnalysis.Storage;
using Microsoft.Isam.Esent.Interop;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentPersistentStorageService : AbstractPersistentStorageService
{
private const string StorageExtension = "vbcs.cache";
private const string PersistentStorageFileName = "storage.ide";
public EsentPersistentStorageService(
IOptionService optionService,
SolutionSizeTracker solutionSizeTracker)
: base(optionService, solutionSizeTracker)
{
}
public EsentPersistentStorageService(IOptionService optionService, bool testing)
: base(optionService, testing)
{
}
protected override string GetDatabaseFilePath(string workingFolderPath)
{
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(workingFolderPath));
return Path.Combine(workingFolderPath, StorageExtension, PersistentStorageFileName);
}
protected override AbstractPersistentStorage OpenDatabase(Solution solution, string workingFolderPath, string databaseFilePath)
=> new EsentPersistentStorage(
OptionService, workingFolderPath, solution.FilePath, databaseFilePath, this.Release);
protected override bool ShouldDeleteDatabase(Exception exception)
{
// Access denied can happen when some other process is holding onto the DB.
// Don't want to delete it in that case. For all other cases, delete the db.
return !(exception is EsentAccessDeniedException);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Isam.Esent.Interop;
using Microsoft.Isam.Esent.Interop.Vista;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public abstract class AbstractTable
{
protected const string PrimaryIndexName = "PrimaryIndex";
public abstract void Create(JET_SESID sessionId, JET_DBID databaseId);
public abstract void Initialize(JET_SESID sessionId, JET_DBID databaseId);
public abstract AbstractTableAccessor GetTableAccessor(OpenSession openSession);
protected JET_COLUMNCREATE CreateAutoIncrementIdColumn(string columnName)
{
return new JET_COLUMNCREATE()
{
szColumnName = columnName,
coltyp = JET_coltyp.Long,
grbit = ColumndefGrbit.ColumnAutoincrement | ColumndefGrbit.ColumnNotNULL
};
}
protected JET_COLUMNCREATE CreateIdColumn(string columnName)
{
return new JET_COLUMNCREATE()
{
szColumnName = columnName,
coltyp = JET_coltyp.Long,
grbit = ColumndefGrbit.ColumnNotNULL
};
}
protected JET_COLUMNCREATE CreateBinaryColumn(string columnName)
{
return new JET_COLUMNCREATE()
{
szColumnName = columnName,
coltyp = JET_coltyp.LongBinary,
grbit = ColumndefGrbit.None
};
}
protected JET_COLUMNCREATE CreateTextColumn(string columnName)
{
return new JET_COLUMNCREATE()
{
szColumnName = columnName,
coltyp = JET_coltyp.LongText,
cp = JET_CP.Unicode,
grbit = ColumndefGrbit.ColumnNotNULL
};
}
protected string CreateIndexKey(params string[] indexNames)
{
return "+" + string.Join("\0+", indexNames) + "\0\0";
}
protected JET_INDEXCREATE CreatePrimaryIndex(string indexKey)
{
return new JET_INDEXCREATE
{
szIndexName = PrimaryIndexName,
szKey = indexKey,
cbKey = indexKey.Length,
grbit = CreateIndexGrbit.IndexPrimary | CreateIndexGrbit.IndexUnique | CreateIndexGrbit.IndexDisallowNull,
ulDensity = 80
};
}
protected JET_INDEXCREATE CreateIndex(string name, string indexKey)
{
return new JET_INDEXCREATE
{
szIndexName = name,
szKey = indexKey,
cbKey = indexKey.Length,
grbit = CreateIndexGrbit.IndexDisallowNull,
ulDensity = 80
};
}
protected JET_INDEXCREATE CreateUniqueTextIndex(string name, string indexKey)
{
return new JET_INDEXCREATE
{
szIndexName = name,
szKey = indexKey,
cbKey = indexKey.Length,
grbit = CreateIndexGrbit.IndexUnique | CreateIndexGrbit.IndexDisallowNull | VistaGrbits.IndexDisallowTruncation,
ulDensity = 80,
// this should be 2000 bytes Max after vista
cbKeyMost = SystemParameters.KeyMost
};
}
protected JET_TABLECREATE CreateTable(string tableName, JET_COLUMNCREATE[] columns, JET_INDEXCREATE[] indexes)
{
return new JET_TABLECREATE()
{
szTableName = tableName,
ulPages = 16,
ulDensity = 80,
rgcolumncreate = columns,
cColumns = columns.Length,
rgindexcreate = indexes,
cIndexes = indexes.Length
};
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Isam.Esent.Interop;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public abstract class AbstractTableAccessor : IDisposable
{
private readonly OpenSession _session;
private readonly string _tableName;
private Table _table;
private Transaction _transaction;
private Update _updater;
protected AbstractTableAccessor(OpenSession session, string tableName)
{
_session = session;
_tableName = tableName;
}
protected void OpenTableForReading()
{
Contract.ThrowIfFalse(_table == null);
_table = new Table(_session.SessionId, _session.DatabaseId, _tableName, OpenTableGrbit.ReadOnly);
}
protected void OpenTableForUpdating()
{
OpenTableForUpdatingWithoutTransaction();
_transaction = new Transaction(_session.SessionId);
}
protected void OpenTableForUpdatingWithoutTransaction()
{
Contract.ThrowIfFalse(_table == null);
_table = new Table(_session.SessionId, _session.DatabaseId, _tableName, OpenTableGrbit.Updatable);
}
protected void EnsureTableForUpdating()
{
if (_table == null)
{
_table = new Table(_session.SessionId, _session.DatabaseId, _tableName, OpenTableGrbit.Updatable);
}
if (_transaction == null)
{
_transaction = new Transaction(_session.SessionId);
}
}
protected void PrepareUpdate(JET_prep mode)
{
Contract.ThrowIfFalse(_table != null);
if (_updater != null)
{
_updater.Dispose();
}
_updater = new Update(_session.SessionId, TableId, mode);
}
protected JET_SESID SessionId
{
get
{
return _session.SessionId;
}
}
protected JET_TABLEID TableId
{
get
{
// one of OpenTable should have been called. otherwise, throw exception
return _table.JetTableid;
}
}
public void Flush()
{
if (_transaction == null)
{
return;
}
// commit existing transaction
_transaction.Commit(CommitTransactionGrbit.LazyFlush);
_transaction.Dispose();
_transaction = null;
}
public bool ApplyChanges()
{
try
{
var local = _updater;
_updater = null;
if (local != null)
{
local.Save();
local.Dispose();
}
}
catch (EsentKeyDuplicateException)
{
return false;
}
catch (EsentWriteConflictException)
{
return false;
}
Flush();
return true;
}
public void Dispose()
{
if (_updater != null)
{
_updater.Dispose();
}
if (_transaction != null)
{
_transaction.Dispose();
}
if (_table != null)
{
_table.Dispose();
}
_session.Dispose();
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Isam.Esent.Interop;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public class DocumentTable : ProjectDocumentTable
{
private const string TableName = "DocumentTable";
private const string NameColumnName = "Name";
private const string ValueColumnName = "Value";
private JET_COLUMNID _projectColumnId;
private JET_COLUMNID _projectNameColumnId;
private JET_COLUMNID _documentColumnId;
private JET_COLUMNID _nameColumnId;
private JET_COLUMNID _valueColumnId;
public override void Create(JET_SESID sessionId, JET_DBID databaseId)
{
var nameColumnCreate = CreateIdColumn(NameColumnName);
var valueColumnCreate = CreateBinaryColumn(ValueColumnName);
var columns = CreateProjectDocumentColumns(nameColumnCreate, valueColumnCreate);
var primaryIndexKey = CreateProjectDocumentIndexKey(NameColumnName);
var indexes = new JET_INDEXCREATE[]
{
CreatePrimaryIndex(primaryIndexKey)
};
var tableCreate = CreateTable(TableName, columns, indexes);
Api.JetCreateTableColumnIndex3(sessionId, databaseId, tableCreate);
GetColumnIds(columns, out _projectColumnId, out _projectNameColumnId, out _documentColumnId);
_nameColumnId = nameColumnCreate.columnid;
_valueColumnId = valueColumnCreate.columnid;
Api.JetCloseTable(sessionId, tableCreate.tableid);
}
public override void Initialize(JET_SESID sessionId, JET_DBID databaseId)
{
using (var table = new Table(sessionId, databaseId, TableName, OpenTableGrbit.ReadOnly))
{
GetColumnIds(sessionId, table, out _projectColumnId, out _projectNameColumnId, out _documentColumnId);
_nameColumnId = Api.GetTableColumnid(sessionId, table, NameColumnName);
_valueColumnId = Api.GetTableColumnid(sessionId, table, ValueColumnName);
}
}
public override AbstractTableAccessor GetTableAccessor(OpenSession openSession)
{
return new DocumentTableAccessor(
openSession, TableName, PrimaryIndexName,
_projectColumnId, _projectNameColumnId, _documentColumnId, _nameColumnId, _valueColumnId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public class DocumentTableAccessor : ProjectDocumentTableAccessor
{
private readonly JET_COLUMNID _nameColumnId;
private readonly JET_COLUMNID _valueColumnId;
public DocumentTableAccessor(
OpenSession session, string tableName, string primaryIndexName,
JET_COLUMNID projectColumnId, JET_COLUMNID projectNameColumnId,
JET_COLUMNID documentColumnId, JET_COLUMNID nameColumnId, JET_COLUMNID valueColumnId) :
base(session, tableName, primaryIndexName, projectColumnId, projectNameColumnId, documentColumnId)
{
_nameColumnId = nameColumnId;
_valueColumnId = valueColumnId;
}
private bool TrySeek(Key key, int nameId)
{
return TrySeek(IndexName, key, nameId);
}
public override Stream GetReadStream(Key key, int nameId)
{
OpenTableForReading();
if (TrySeek(key, nameId))
{
return new ColumnStream(SessionId, TableId, _valueColumnId);
}
return null;
}
public override Stream GetWriteStream(Key key, int nameId)
{
OpenTableForUpdating();
if (TrySeek(key, nameId))
{
PrepareUpdate(JET_prep.ReplaceNoLock);
}
else
{
PrepareUpdate(JET_prep.Insert);
var args = GetColumnValues(key, _nameColumnId, nameId);
Api.SetColumns(SessionId, TableId, args);
Free(args);
}
return new ColumnStream(SessionId, TableId, _valueColumnId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public class IdentifierLocationTable : ProjectDocumentTable
{
private const string TableName = "IdentifierLocationTable";
private const string IdentifierColumnName = "Identifier";
private const string LocationsColumnName = "Locations";
private JET_COLUMNID _projectColumnId;
private JET_COLUMNID _projectNameColumnId;
private JET_COLUMNID _documentColumnId;
private JET_COLUMNID _identifierColumnId;
private JET_COLUMNID _locationsColumnId;
public override void Create(JET_SESID sessionId, JET_DBID databaseId)
{
var identifierColumnCreate = CreateIdColumn(IdentifierColumnName);
var locationsColumnCreate = CreateBinaryColumn(LocationsColumnName);
var columns = CreateProjectDocumentColumns(identifierColumnCreate, locationsColumnCreate);
var primaryIndexKey = CreateProjectDocumentIndexKey(IdentifierColumnName);
var indexes = new JET_INDEXCREATE[]
{
CreatePrimaryIndex(primaryIndexKey)
};
var tableCreate = CreateTable(TableName, columns, indexes);
Api.JetCreateTableColumnIndex3(sessionId, databaseId, tableCreate);
GetColumnIds(columns, out _projectColumnId, out _projectNameColumnId, out _documentColumnId);
_identifierColumnId = identifierColumnCreate.columnid;
_locationsColumnId = locationsColumnCreate.columnid;
Api.JetCloseTable(sessionId, tableCreate.tableid);
}
public override void Initialize(JET_SESID sessionId, JET_DBID databaseId)
{
using (var table = new Table(sessionId, databaseId, TableName, OpenTableGrbit.ReadOnly))
{
GetColumnIds(sessionId, table, out _projectColumnId, out _projectNameColumnId, out _documentColumnId);
_identifierColumnId = Api.GetTableColumnid(sessionId, table, IdentifierColumnName);
_locationsColumnId = Api.GetTableColumnid(sessionId, table, LocationsColumnName);
}
}
public override AbstractTableAccessor GetTableAccessor(OpenSession openSession)
{
return new IdentifierLocationTableAccessor(
openSession, TableName, PrimaryIndexName,
_projectColumnId, _projectNameColumnId, _documentColumnId, _identifierColumnId, _locationsColumnId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public class IdentifierLocationTableAccessor : ProjectDocumentTableAccessor
{
private readonly JET_COLUMNID _identifierColumnId;
private readonly JET_COLUMNID _valueColumnId;
public IdentifierLocationTableAccessor(
OpenSession session, string tableName, string primaryIndexName,
JET_COLUMNID projectColumnId, JET_COLUMNID projectNameColumnId, JET_COLUMNID documentColumnId,
JET_COLUMNID identifierColumnId, JET_COLUMNID valueColumnId) :
base(session, tableName, primaryIndexName, projectColumnId, projectNameColumnId, documentColumnId)
{
_identifierColumnId = identifierColumnId;
_valueColumnId = valueColumnId;
}
private bool TrySeek(Key key, int identifierId)
{
return TrySeek(IndexName, key, identifierId);
}
public bool Contains(Key key, int identifierId)
{
OpenTableForReading();
return TrySeek(key, identifierId);
}
public void Delete(Key key, CancellationToken cancellationToken)
{
OpenTableForUpdating();
// set upper bound using index
Api.JetSetCurrentIndex(SessionId, TableId, IndexName);
MakeKey(key);
// put the cursor at the first record and set range for exact match
if (!Api.TrySeek(SessionId, TableId, SeekGrbit.SeekEQ))
{
return;
}
MakeKey(key);
if (!Api.TrySetIndexRange(SessionId, TableId, SetIndexRangeGrbit.RangeInclusive | SetIndexRangeGrbit.RangeUpperLimit))
{
return;
}
// delete all matching record
do
{
cancellationToken.ThrowIfCancellationRequested();
Api.JetDelete(SessionId, TableId);
}
while (Api.TryMoveNext(SessionId, TableId));
}
public override Stream GetReadStream(Key key, int nameId)
{
OpenTableForReading();
if (TrySeek(key, nameId))
{
return new ColumnStream(SessionId, TableId, _valueColumnId);
}
return null;
}
public void PrepareBatchOneInsert()
{
EnsureTableForUpdating();
Api.JetPrepareUpdate(SessionId, TableId, JET_prep.Insert);
}
public void FinishBatchOneInsert()
{
Api.JetUpdate(SessionId, TableId);
}
public override Stream GetWriteStream(Key key, int nameId)
{
var args = GetColumnValues(key, _identifierColumnId, nameId);
Api.SetColumns(SessionId, TableId, args);
Free(args);
return new ColumnStream(SessionId, TableId, _valueColumnId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
private class IdentifierNameTable : AbstractTable
{
private const string TableName = "IdentifierTable";
private const string IdColumnName = "Id";
private const string IdentifierIndexName = "IdentifierIndex";
private const string IdentifierColumnName = "Identifier";
private JET_COLUMNID _idColumnId;
private JET_COLUMNID _identifierColumnId;
public override void Create(JET_SESID sessionId, JET_DBID databaseId)
{
var idColumnCreate = CreateAutoIncrementIdColumn(IdColumnName);
var identifierColumnCreate = CreateTextColumn(IdentifierColumnName);
var columns = new JET_COLUMNCREATE[] { idColumnCreate, identifierColumnCreate };
var primaryIndexKey = CreateIndexKey(IdColumnName);
var identifierIndexKey = CreateIndexKey(IdentifierColumnName);
var indexes = new JET_INDEXCREATE[]
{
CreatePrimaryIndex(primaryIndexKey),
CreateUniqueTextIndex(IdentifierIndexName, identifierIndexKey)
};
var tableCreate = CreateTable(TableName, columns, indexes);
Api.JetCreateTableColumnIndex3(sessionId, databaseId, tableCreate);
_idColumnId = idColumnCreate.columnid;
_identifierColumnId = identifierColumnCreate.columnid;
Api.JetCloseTable(sessionId, tableCreate.tableid);
}
public override void Initialize(JET_SESID sessionId, JET_DBID databaseId)
{
using (var table = new Table(sessionId, databaseId, TableName, OpenTableGrbit.ReadOnly))
{
_idColumnId = Api.GetTableColumnid(sessionId, table, IdColumnName);
_identifierColumnId = Api.GetTableColumnid(sessionId, table, IdentifierColumnName);
}
}
public override AbstractTableAccessor GetTableAccessor(OpenSession openSession)
{
return new StringNameTableAccessor(openSession, TableName, IdentifierIndexName, _idColumnId, _identifierColumnId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
private class NameTable : AbstractTable
{
private const string TableName = "NameTable";
private const string IdColumnName = "Id";
private const string NameIndexName = "NameIndex";
private const string NameColumnName = "Name";
private JET_COLUMNID _idColumnId;
private JET_COLUMNID _nameColumnId;
public override void Create(JET_SESID sessionId, JET_DBID databaseId)
{
var idColumnCreate = CreateAutoIncrementIdColumn(IdColumnName);
var nameColumnCreate = CreateTextColumn(NameColumnName);
var columns = new JET_COLUMNCREATE[] { idColumnCreate, nameColumnCreate };
var primaryIndexKey = CreateIndexKey(IdColumnName);
var nameIndexKey = CreateIndexKey(NameColumnName);
var indexes = new JET_INDEXCREATE[]
{
CreatePrimaryIndex(primaryIndexKey),
CreateUniqueTextIndex(NameIndexName, nameIndexKey)
};
var tableCreate = CreateTable(TableName, columns, indexes);
Api.JetCreateTableColumnIndex3(sessionId, databaseId, tableCreate);
_idColumnId = idColumnCreate.columnid;
_nameColumnId = nameColumnCreate.columnid;
Api.JetCloseTable(sessionId, tableCreate.tableid);
}
public override void Initialize(JET_SESID sessionId, JET_DBID databaseId)
{
using (var table = new Table(sessionId, databaseId, TableName, OpenTableGrbit.ReadOnly))
{
_idColumnId = Api.GetTableColumnid(sessionId, table, IdColumnName);
_nameColumnId = Api.GetTableColumnid(sessionId, table, NameColumnName);
}
}
public override AbstractTableAccessor GetTableAccessor(OpenSession openSession)
{
return new StringNameTableAccessor(openSession, TableName, NameIndexName, _idColumnId, _nameColumnId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public class OpenSession : IDisposable
{
#if false
private static int globalId;
private readonly int id;
#endif
private readonly string _databaseFile;
private readonly EsentStorage _storage;
private readonly CancellationToken _shutdownCancellationToken;
private readonly CancellationTokenRegistration _cancellationTokenRegistration;
private Session _session;
private JET_DBID _databaseId;
public OpenSession(EsentStorage storage, string databaseFile, CancellationToken shutdownCancellationToken)
{
_storage = storage;
#if false
id = Interlocked.Increment(ref globalId);
System.Diagnostics.Trace.WriteLine("open sessionId: " + id);
#endif
_session = new Session(storage._instance);
_databaseFile = databaseFile;
_databaseId = OpenExistingDatabase(_session, databaseFile);
_shutdownCancellationToken = shutdownCancellationToken;
_cancellationTokenRegistration = shutdownCancellationToken.Register(() => Dispose(), useSynchronizationContext: false);
}
public JET_SESID SessionId { get { return _session.JetSesid; } }
public JET_DBID DatabaseId { get { return _databaseId; } }
public void Dispose()
{
_storage.CloseSession(this);
}
public void Close()
{
_cancellationTokenRegistration.Dispose();
if (_databaseId != JET_DBID.Nil)
{
Api.JetCloseDatabase(_session, _databaseId, CloseDatabaseGrbit.None);
_databaseId = JET_DBID.Nil;
}
if (_session != null)
{
#if false
System.Diagnostics.Trace.WriteLine("close sessionId: " + id);
#endif
_session.Dispose();
_session = null;
}
}
private JET_DBID OpenExistingDatabase(JET_SESID session, string databaseFile)
{
Api.JetOpenDatabase(SessionId, databaseFile, null, out var databaseId, OpenDatabaseGrbit.None);
return databaseId;
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.Isam.Esent.Interop;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
private static class Pool
{
private static readonly ObjectPool<ColumnValue[]>[] s_columnValuePool = new[]
{
new ObjectPool<ColumnValue[]>(() => new ColumnValue[3], 20),
new ObjectPool<ColumnValue[]>(() => new ColumnValue[4], 20)
};
public static ColumnValue[] GetInt32Columns(JET_COLUMNID columnId1, int value1, JET_COLUMNID columnId2, int value2, JET_COLUMNID columnId3, int value3)
{
var array = s_columnValuePool[0].Allocate();
array[0] = GetInt32Column(columnId1, value1);
array[1] = GetInt32Column(columnId2, value2);
array[2] = GetInt32Column(columnId3, value3);
return array;
}
public static ColumnValue[] GetInt32Columns(JET_COLUMNID columnId1, int value1, JET_COLUMNID columnId2, int value2, JET_COLUMNID columnId3, int value3, JET_COLUMNID columnId4, int value4)
{
var array = s_columnValuePool[1].Allocate();
array[0] = GetInt32Column(columnId1, value1);
array[1] = GetInt32Column(columnId2, value2);
array[2] = GetInt32Column(columnId3, value3);
array[3] = GetInt32Column(columnId4, value4);
return array;
}
public static Int32ColumnValue GetInt32Column(JET_COLUMNID columnId, int value)
{
var column = SharedPools.Default<Int32ColumnValue>().Allocate();
column.Columnid = columnId;
column.Value = value;
return column;
}
public static void Free(ColumnValue[] values)
{
for (int i = 0; i < values.Length; i++)
{
Free(values[i]);
}
s_columnValuePool[values.Length - 3].Free(values);
}
public static void Free(ColumnValue value)
{
var intValue = value as Int32ColumnValue;
if (intValue != null)
{
SharedPools.Default<Int32ColumnValue>().Free(intValue);
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public abstract class ProjectDocumentTable : AbstractTable
{
private const string ProjectColumnName = "Project";
private const string ProjectNameColumnName = "ProjectName";
private const string DocumentColumnName = "Document";
protected JET_COLUMNCREATE[] CreateProjectDocumentColumns(JET_COLUMNCREATE column1, JET_COLUMNCREATE column2)
{
var projectColumnCreate = CreateIdColumn(ProjectColumnName);
var projectNameColumnCreate = CreateIdColumn(ProjectNameColumnName);
var documentColumnCreate = CreateIdColumn(DocumentColumnName);
return new JET_COLUMNCREATE[] {
projectColumnCreate,
projectNameColumnCreate,
documentColumnCreate,
column1, column2 };
}
protected string CreateProjectDocumentIndexKey()
{
return CreateIndexKey(ProjectColumnName, ProjectNameColumnName, DocumentColumnName);
}
protected string CreateProjectDocumentIndexKey(string columnName)
{
return CreateIndexKey(ProjectColumnName, ProjectNameColumnName, DocumentColumnName, columnName);
}
protected void GetColumnIds(
JET_COLUMNCREATE[] columns, out JET_COLUMNID projectColumnId, out JET_COLUMNID projectNameColumnId, out JET_COLUMNID documentColumnId)
{
projectColumnId = columns[0].columnid;
projectNameColumnId = columns[1].columnid;
documentColumnId = columns[2].columnid;
}
protected void GetColumnIds(
JET_SESID sessionId, Table table, out JET_COLUMNID projectColumnId, out JET_COLUMNID projectNameColumnId, out JET_COLUMNID documentColumnId)
{
projectColumnId = Api.GetTableColumnid(sessionId, table, ProjectColumnName);
projectNameColumnId = Api.GetTableColumnid(sessionId, table, ProjectNameColumnName);
documentColumnId = Api.GetTableColumnid(sessionId, table, DocumentColumnName);
}
protected JET_COLUMNCREATE[] CreateProjectColumns(JET_COLUMNCREATE column1, JET_COLUMNCREATE column2)
{
var projectColumnCreate = CreateIdColumn(ProjectColumnName);
var projectNameColumnCreate = CreateIdColumn(ProjectNameColumnName);
return new JET_COLUMNCREATE[] {
projectColumnCreate,
projectNameColumnCreate,
column1, column2 };
}
protected string CreateProjectIndexKey()
{
return CreateIndexKey(ProjectColumnName, ProjectNameColumnName);
}
protected string CreateProjectIndexKey(string columnName)
{
return CreateIndexKey(ProjectColumnName, ProjectNameColumnName, columnName);
}
protected void GetColumnIds(
JET_COLUMNCREATE[] columns, out JET_COLUMNID projectColumnId, out JET_COLUMNID projectNameColumnId)
{
projectColumnId = columns[0].columnid;
projectNameColumnId = columns[1].columnid;
}
protected void GetColumnIds(
JET_SESID sessionId, Table table, out JET_COLUMNID projectColumnId, out JET_COLUMNID projectNameColumnId)
{
projectColumnId = Api.GetTableColumnid(sessionId, table, ProjectColumnName);
projectNameColumnId = Api.GetTableColumnid(sessionId, table, ProjectNameColumnName);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public abstract class ProjectDocumentTableAccessor : AbstractTableAccessor
{
protected readonly JET_COLUMNID ProjectColumnId;
protected readonly JET_COLUMNID ProjectNameColumnId;
protected readonly JET_COLUMNID DocumentColumnId;
protected readonly string IndexName;
public ProjectDocumentTableAccessor(
OpenSession session, string tableName, string indexName,
JET_COLUMNID projectColumnId, JET_COLUMNID projectNameColumnId, JET_COLUMNID documentColumnId) : base(session, tableName)
{
IndexName = indexName;
ProjectColumnId = projectColumnId;
ProjectNameColumnId = projectNameColumnId;
DocumentColumnId = documentColumnId;
}
public abstract Stream GetReadStream(Key key, int nameId);
public abstract Stream GetWriteStream(Key key, int nameId);
protected bool TrySeek(string indexName, Key key, int id1)
{
SetIndexAndMakeKey(indexName, key, id1);
return Api.TrySeek(SessionId, TableId, SeekGrbit.SeekEQ);
}
protected void SetIndexAndMakeKey(string indexName, Key key, int id1)
{
Api.JetSetCurrentIndex(SessionId, TableId, indexName);
MakeKey(key, id1);
}
protected void MakeKey(Key key)
{
Api.MakeKey(SessionId, TableId, key.ProjectId, MakeKeyGrbit.NewKey);
Api.MakeKey(SessionId, TableId, key.ProjectNameId, MakeKeyGrbit.None);
if (key.DocumentIdOpt.HasValue)
{
Api.MakeKey(SessionId, TableId, key.DocumentIdOpt.Value, MakeKeyGrbit.None);
}
}
protected void MakeKey(Key key, int id1)
{
MakeKey(key);
Api.MakeKey(SessionId, TableId, id1, MakeKeyGrbit.None);
}
protected ColumnValue[] GetColumnValues(Key key, JET_COLUMNID columnId1, int id1)
{
if (key.DocumentIdOpt.HasValue)
{
return Pool.GetInt32Columns(ProjectColumnId, key.ProjectId, ProjectNameColumnId, key.ProjectNameId, DocumentColumnId, key.DocumentIdOpt.Value, columnId1, id1);
}
return Pool.GetInt32Columns(ProjectColumnId, key.ProjectId, ProjectNameColumnId, key.ProjectNameId, columnId1, id1);
}
protected void Free(ColumnValue[] values)
{
Pool.Free(values);
}
}
public struct Key
{
public int ProjectId;
public int ProjectNameId;
public int? DocumentIdOpt;
public Key(int projectId, int projectNameId, int documentId)
{
ProjectId = projectId;
ProjectNameId = projectNameId;
DocumentIdOpt = documentId;
}
public Key(int projectId, int projectNameId)
{
ProjectId = projectId;
ProjectNameId = projectNameId;
DocumentIdOpt = null;
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
private class ProjectTable : ProjectDocumentTable
{
private const string TableName = "ProjectTable";
private const string NameColumnName = "Name";
private const string ValueColumnName = "Value";
private JET_COLUMNID _projectColumnId;
private JET_COLUMNID _projectNameColumnId;
private JET_COLUMNID _nameColumnId;
private JET_COLUMNID _valueColumnId;
public override void Create(JET_SESID sessionId, JET_DBID databaseId)
{
var nameColumnCreate = CreateIdColumn(NameColumnName);
var valueColumnCreate = CreateBinaryColumn(ValueColumnName);
var columns = CreateProjectColumns(nameColumnCreate, valueColumnCreate);
var projectAndNameIndexKey = CreateProjectIndexKey(NameColumnName);
var indexes = new JET_INDEXCREATE[]
{
CreatePrimaryIndex(projectAndNameIndexKey)
};
var tableCreate = CreateTable(TableName, columns, indexes);
Api.JetCreateTableColumnIndex3(sessionId, databaseId, tableCreate);
GetColumnIds(columns, out _projectColumnId, out _projectNameColumnId);
_nameColumnId = nameColumnCreate.columnid;
_valueColumnId = valueColumnCreate.columnid;
Api.JetCloseTable(sessionId, tableCreate.tableid);
}
public override void Initialize(JET_SESID sessionId, JET_DBID databaseId)
{
using (var table = new Table(sessionId, databaseId, TableName, OpenTableGrbit.ReadOnly))
{
GetColumnIds(sessionId, table, out _projectColumnId, out _projectNameColumnId);
_nameColumnId = Api.GetTableColumnid(sessionId, table, NameColumnName);
_valueColumnId = Api.GetTableColumnid(sessionId, table, ValueColumnName);
}
}
public override AbstractTableAccessor GetTableAccessor(OpenSession openSession)
{
return new ProjectTableAccessor(openSession, TableName, PrimaryIndexName, _projectColumnId, _projectNameColumnId, _nameColumnId, _valueColumnId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Text;
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public class ProjectTableAccessor : ProjectDocumentTableAccessor
{
private readonly JET_COLUMNID _nameColumnId;
private readonly JET_COLUMNID _valueColumnId;
public ProjectTableAccessor(
OpenSession session, string tableName, string indexName,
JET_COLUMNID projectColumnId, JET_COLUMNID projectNameColumnId, JET_COLUMNID nameColumnId, JET_COLUMNID valueColumnId) :
base(session, tableName, indexName, projectColumnId, projectNameColumnId, default)
{
_nameColumnId = nameColumnId;
_valueColumnId = valueColumnId;
}
private bool TrySeek(Key key, int nameId)
{
return TrySeek(IndexName, key, nameId);
}
public override Stream GetReadStream(Key key, int nameId)
{
OpenTableForReading();
if (TrySeek(key, nameId))
{
return new ColumnStream(SessionId, TableId, _valueColumnId);
}
return null;
}
public override Stream GetWriteStream(Key key, int nameId)
{
OpenTableForUpdating();
if (TrySeek(key, nameId))
{
PrepareUpdate(JET_prep.ReplaceNoLock);
}
else
{
PrepareUpdate(JET_prep.Insert);
var args = GetColumnValues(key, _nameColumnId, nameId);
Api.SetColumns(SessionId, TableId, args);
Free(args);
}
return new ColumnStream(SessionId, TableId, _valueColumnId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Isam.Esent.Interop;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
private class SolutionTable : AbstractTable
{
private const string TableName = "SolutionTable";
private const string NameColumnName = "Name";
private const string ValueColumnName = "Value";
private JET_COLUMNID _nameColumnId;
private JET_COLUMNID _valueColumnId;
public override void Create(JET_SESID sessionId, JET_DBID databaseId)
{
var nameColumnCreate = CreateIdColumn(NameColumnName);
var valueColumnCreate = CreateBinaryColumn(ValueColumnName);
var columns = new JET_COLUMNCREATE[] { nameColumnCreate, valueColumnCreate };
var nameIndexKey = CreateIndexKey(NameColumnName);
var indexes = new JET_INDEXCREATE[]
{
CreatePrimaryIndex(nameIndexKey)
};
var tableCreate = CreateTable(TableName, columns, indexes);
Api.JetCreateTableColumnIndex3(sessionId, databaseId, tableCreate);
_nameColumnId = nameColumnCreate.columnid;
_valueColumnId = valueColumnCreate.columnid;
Api.JetCloseTable(sessionId, tableCreate.tableid);
}
public override void Initialize(JET_SESID sessionId, JET_DBID databaseId)
{
using (var table = new Table(sessionId, databaseId, TableName, OpenTableGrbit.ReadOnly))
{
_nameColumnId = Api.GetTableColumnid(sessionId, table, NameColumnName);
_valueColumnId = Api.GetTableColumnid(sessionId, table, ValueColumnName);
}
}
public override AbstractTableAccessor GetTableAccessor(OpenSession openSession)
{
return new SolutionTableAccessor(openSession, TableName, PrimaryIndexName, _nameColumnId, _valueColumnId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Text;
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public class SolutionTableAccessor : AbstractTableAccessor
{
private readonly JET_COLUMNID _nameColumnId;
private readonly JET_COLUMNID _valueColumnId;
private readonly string _indexName;
public SolutionTableAccessor(
OpenSession session, string tableName, string indexName, JET_COLUMNID nameColumnId, JET_COLUMNID valueColumnId) : base(session, tableName)
{
_indexName = indexName;
_nameColumnId = nameColumnId;
_valueColumnId = valueColumnId;
}
protected bool TrySeek(int nameId)
{
Api.JetSetCurrentIndex(SessionId, TableId, _indexName);
Api.MakeKey(SessionId, TableId, nameId, MakeKeyGrbit.NewKey);
return Api.TrySeek(SessionId, TableId, SeekGrbit.SeekEQ);
}
public Stream GetReadStream(int nameId)
{
OpenTableForReading();
if (TrySeek(nameId))
{
return new ColumnStream(SessionId, TableId, _valueColumnId);
}
return null;
}
public Stream GetWriteStream(int nameId)
{
OpenTableForUpdating();
if (TrySeek(nameId))
{
PrepareUpdate(JET_prep.ReplaceNoLock);
}
else
{
PrepareUpdate(JET_prep.Insert);
Api.SetColumn(SessionId, TableId, _nameColumnId, nameId);
}
return new ColumnStream(SessionId, TableId, _valueColumnId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
using Microsoft.Isam.Esent.Interop;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
public class StringNameTableAccessor : AbstractTableAccessor
{
private readonly JET_COLUMNID _idColumnId;
private readonly JET_COLUMNID _nameColumnId;
private readonly string _indexName;
public StringNameTableAccessor(
OpenSession session, string tableName, string indexName, JET_COLUMNID idColumnId, JET_COLUMNID nameColumnId) : base(session, tableName)
{
_indexName = indexName;
_idColumnId = idColumnId;
_nameColumnId = nameColumnId;
}
private bool TrySeek(string value)
{
Api.JetSetCurrentIndex(SessionId, TableId, _indexName);
Api.MakeKey(SessionId, TableId, value, Encoding.Unicode, MakeKeyGrbit.NewKey);
return Api.TrySeek(SessionId, TableId, SeekGrbit.SeekEQ);
}
public int GetUniqueId(string value)
{
OpenTableForUpdatingWithoutTransaction();
while (true)
{
if (TrySeek(value))
{
return Api.RetrieveColumnAsInt32(SessionId, TableId, _idColumnId).Value;
}
using (var transaction = new Transaction(SessionId))
{
PrepareUpdate(JET_prep.Insert);
var id = Api.RetrieveColumnAsInt32(SessionId, TableId, _idColumnId, RetrieveColumnGrbit.RetrieveCopy).Value;
// set name
Api.SetColumn(SessionId, TableId, _nameColumnId, value, Encoding.Unicode);
if (ApplyChanges())
{
transaction.Commit(CommitTransactionGrbit.LazyFlush);
return id;
}
}
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
private enum TableKinds
{
Name,
Solution,
Project,
Document,
Identifier,
IdentifierLocations
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Microsoft.Isam.Esent.Interop;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Esent
{
internal partial class EsentStorage
{
// set db page size to 8K and version store page size to 16K
private const int DatabasePageSize = 2 * 4 * 1024;
private const int VersionStorePageSize = 4 * 4 * 1024;
// JET parameter consts
private const int JET_paramIOPriority = 152;
private const int JET_paramCheckpointIOMax = 135;
private const int JET_paramVerPageSize = 128;
private const int JET_paramDisablePerfmon = 107;
private const int JET_paramPageHintCacheSize = 101;
private const int JET_paramLogFileCreateAsynch = 69;
private const int JET_paramOutstandingIOMax = 30;
private const int JET_paramLRUKHistoryMax = 26;
private const int JET_paramCommitDefault = 16;
private readonly string _databaseFile;
private readonly bool _enablePerformanceMonitor;
private readonly Dictionary<TableKinds, AbstractTable> _tables;
private readonly ConcurrentStack<OpenSession> _sessionCache;
private readonly CancellationTokenSource _shutdownCancellationTokenSource;
private Instance _instance;
private Session _primarySessionId;
private JET_DBID _primaryDatabaseId;
public EsentStorage(string databaseFile, bool enablePerformanceMonitor = false)
{
Contract.Requires(!string.IsNullOrWhiteSpace(databaseFile));
_databaseFile = databaseFile;
_enablePerformanceMonitor = enablePerformanceMonitor;
// order of tables are fixed. don't change it
_tables = new Dictionary<TableKinds, AbstractTable>()
{
{ TableKinds.Name, new NameTable() },
{ TableKinds.Solution, new SolutionTable() },
{ TableKinds.Project, new ProjectTable() },
{ TableKinds.Document, new DocumentTable() },
{ TableKinds.Identifier, new IdentifierNameTable() },
{ TableKinds.IdentifierLocations, new IdentifierLocationTable() },
};
_sessionCache = new ConcurrentStack<OpenSession>();
_shutdownCancellationTokenSource = new CancellationTokenSource();
}
public void Initialize()
{
_instance = CreateEsentInstance();
_primarySessionId = new Session(_instance);
InitializeDatabaseAndTables();
}
public bool IsClosed
{
get { return _instance == null; }
}
public void Close()
{
if (_instance != null)
{
var handle = _instance;
_instance = null;
// try to free all allocated session - if succeeded we can try to do a clean shutdown
_shutdownCancellationTokenSource.Cancel();
try
{
// just close the instance - all associated objects will be closed as well
_primarySessionId.Dispose();
handle.Dispose();
}
catch
{
// ignore exception if whatever reason esent throws an exception.
}
_shutdownCancellationTokenSource.Dispose();
}
}
public int GetUniqueId(string value)
{
return GetUniqueId(value, TableKinds.Name);
}
public int GetUniqueIdentifierId(string value)
{
return GetUniqueId(value, TableKinds.Identifier);
}
private int GetUniqueId(string value, TableKinds tableKind)
{
Contract.ThrowIfTrue(string.IsNullOrWhiteSpace(value));
using (var accessor = (StringNameTableAccessor)GetTableAccessor(tableKind))
{
return accessor.GetUniqueId(value);
}
}
public SolutionTableAccessor GetSolutionTableAccessor()
{
return (SolutionTableAccessor)GetTableAccessor(TableKinds.Solution);
}
public ProjectTableAccessor GetProjectTableAccessor()
{
return (ProjectTableAccessor)GetTableAccessor(TableKinds.Project);
}
public DocumentTableAccessor GetDocumentTableAccessor()
{
return (DocumentTableAccessor)GetTableAccessor(TableKinds.Document);
}
public IdentifierLocationTableAccessor GetIdentifierLocationTableAccessor()
{
return (IdentifierLocationTableAccessor)GetTableAccessor(TableKinds.IdentifierLocations);
}
private AbstractTableAccessor GetTableAccessor(TableKinds tableKind)
{
return _tables[tableKind].GetTableAccessor(GetOpenSession());
}
private OpenSession GetOpenSession()
{
if (_sessionCache.TryPop(out var session))
{
return session;
}
return new OpenSession(this, _databaseFile, _shutdownCancellationTokenSource.Token);
}
private void CloseSession(OpenSession session)
{
if (_shutdownCancellationTokenSource.IsCancellationRequested)
{
session.Close();
return;
}
if (_sessionCache.Count > 5)
{
session.Close();
return;
}
_sessionCache.Push(session);
}
private Instance CreateEsentInstance()
{
var instanceDataFolder = Path.GetDirectoryName(_databaseFile);
TryInitializeGlobalParameters();
var instance = new Instance(Path.GetFileName(_databaseFile), _databaseFile, TermGrbit.Complete);
// create log file preemptively
Api.JetSetSystemParameter(instance.JetInstance, JET_SESID.Nil, (JET_param)JET_paramLogFileCreateAsynch, /* true */ 1, null);
// set default commit mode
Api.JetSetSystemParameter(instance.JetInstance, JET_SESID.Nil, (JET_param)JET_paramCommitDefault, /* lazy */ 1, null);
// remove transaction log file that is not needed anymore
instance.Parameters.CircularLog = true;
// transaction log file buffer 1M (1024 * 2 * 512 bytes)
instance.Parameters.LogBuffers = 2 * 1024;
// transaction log file is 2M (2 * 1024 * 1024 bytes)
instance.Parameters.LogFileSize = 2 * 1024;
// db directories
instance.Parameters.LogFileDirectory = instanceDataFolder;
instance.Parameters.SystemDirectory = instanceDataFolder;
instance.Parameters.TempDirectory = instanceDataFolder;
// Esent uses version pages to store intermediate non-committed data during transactions
// smaller values may cause VersionStoreOutOfMemory error when dealing with multiple transactions\writing lot's of data in transaction or both
// it is about 16MB - this is okay to be big since most of it is temporary memory that will be released once the last transaction goes away
instance.Parameters.MaxVerPages = 16 * 1024 * 1024 / VersionStorePageSize;
// set the size of max transaction log size (in bytes) that should be replayed after the crash
// small values: smaller log files but potentially longer transaction flushes if there was a crash (6M)
instance.Parameters.CheckpointDepthMax = 6 * 1024 * 1024;
// how much db grows when it finds db is full (1M)
// (less I/O as value gets bigger)
instance.Parameters.DbExtensionSize = 1024 * 1024 / DatabasePageSize;
// fail fast if log file is wrong. we will recover from it by creating db from scratch
instance.Parameters.CleanupMismatchedLogFiles = true;
instance.Parameters.EnableIndexChecking = true;
// now, actually initialize instance
instance.Init();
return instance;
}
private void TryInitializeGlobalParameters()
{
Api.JetGetInstanceInfo(out var instances, out var infos);
// already initialized nothing we can do.
if (instances != 0)
{
return;
}
try
{
// use small configuration so that esent use process heap and windows file cache
SystemParameters.Configuration = 0;
// allow many esent instances
SystemParameters.MaxInstances = 1024;
// enable perf monitor if requested
Api.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, (JET_param)JET_paramDisablePerfmon, _enablePerformanceMonitor ? 0 : 1, null);
// set max IO queue (bigger value better IO perf)
Api.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, (JET_param)JET_paramOutstandingIOMax, 1024, null);
// set max current write to db
Api.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, (JET_param)JET_paramCheckpointIOMax, 32, null);
// better cache management (4M)
Api.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, (JET_param)JET_paramLRUKHistoryMax, 4 * 1024 * 1024 / DatabasePageSize, null);
// better db performance (100K bytes)
Api.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, (JET_param)JET_paramPageHintCacheSize, 100 * 1024, null);
// set version page size to normal 16K
Api.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, (JET_param)JET_paramVerPageSize, VersionStorePageSize, null);
// use windows file system cache
SystemParameters.EnableFileCache = true;
// don't use mapped file for database. this will waste more VM.
SystemParameters.EnableViewCache = false;
// this is the unit where chunks are loaded into memory/locked and etc
SystemParameters.DatabasePageSize = DatabasePageSize;
// set max cache size - don't use too much memory for cache (8MB)
SystemParameters.CacheSizeMax = 8 * 1024 * 1024 / DatabasePageSize;
// set min cache size - Esent tries to adjust this value automatically but often it is better to help him.
// small cache sizes => more I\O during random seeks
// currently set to 2MB
SystemParameters.CacheSizeMin = 2 * 1024 * 1024 / DatabasePageSize;
// set box of when cache eviction starts (1% - 2% of max cache size)
SystemParameters.StartFlushThreshold = 20;
SystemParameters.StopFlushThreshold = 40;
}
catch (EsentAlreadyInitializedException)
{
// can't change global status
}
}
private void InitializeDatabaseAndTables()
{
// open database for the first time: database file will be created if necessary
// first quick check whether file exist
if (!File.Exists(_databaseFile))
{
Api.JetCreateDatabase(_primarySessionId, _databaseFile, null, out _primaryDatabaseId, CreateDatabaseGrbit.None);
CreateTables();
return;
}
// file exist, just attach the db.
try
{
// if this succeed, it will lock the file.
Api.JetAttachDatabase(_primarySessionId, _databaseFile, AttachDatabaseGrbit.None);
}
catch (EsentFileNotFoundException)
{
// if someone has deleted the file, while we are attaching.
Api.JetCreateDatabase(_primarySessionId, _databaseFile, null, out _primaryDatabaseId, CreateDatabaseGrbit.None);
CreateTables();
return;
}
Api.JetOpenDatabase(_primarySessionId, _databaseFile, null, out _primaryDatabaseId, OpenDatabaseGrbit.None);
InitializeTables();
}
private void CreateTables()
{
foreach (var table in _tables.Values)
{
table.Create(_primarySessionId, _primaryDatabaseId);
}
}
private void InitializeTables()
{
foreach (var table in _tables.Values)
{
table.Initialize(_primarySessionId, _primaryDatabaseId);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Composition;
using System.Threading;
using Microsoft.CodeAnalysis.Esent;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
......@@ -47,8 +44,6 @@ private IPersistentStorageService GetPersistentStorageService(HostWorkspaceServi
{
case StorageDatabase.SQLite:
return new SQLitePersistentStorageService(optionService, _solutionSizeTracker);
case StorageDatabase.Esent:
return new EsentPersistentStorageService(optionService, _solutionSizeTracker);
case StorageDatabase.None:
default:
return NoOpPersistentStorageService.Instance;
......
......@@ -6,6 +6,5 @@ internal enum StorageDatabase
{
None = 0,
SQLite = 1,
Esent = 2,
}
}
......@@ -21,7 +21,6 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Xml" />
<PackageReference Include="ManagedEsent" Version="$(ManagedEsentVersion)" />
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="$(SQLitePCLRawbundle_greenVersion)" />
<PackageReference Include="Microsoft.Build" Version="$(MicrosoftBuildVersion)" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(MicrosoftBuildTasksCoreVersion)" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册