BsonDocumentStorage et al

master
Harald Wolff 2022-06-03 13:07:13 +02:00
commit 1dbc057620
34 changed files with 2827 additions and 0 deletions

5
.gitignore vendored 100644
View File

@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

View File

@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/projectSettingsUpdater.xml
/modules.xml
/.idea.ln.bson.iml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

34
ln.bson.sln 100644
View File

@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.bson", "ln.bson\ln.bson.csproj", "{F01BEC8F-5554-4738-B200-C86547FE3464}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.bson.tests", "ln.bson.tests\ln.bson.tests.csproj", "{D8C856D0-BA32-47A4-A334-71C9A1C66B07}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.bson.storage", "ln.bson.storage\ln.bson.storage.csproj", "{FEB2C12B-7A0F-468C-9063-980FC4A4B8BE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.collections", "..\ln.collections\ln.collections\ln.collections.csproj", "{168FA1B8-0C0D-4AAE-B026-F1A736027C03}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F01BEC8F-5554-4738-B200-C86547FE3464}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F01BEC8F-5554-4738-B200-C86547FE3464}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F01BEC8F-5554-4738-B200-C86547FE3464}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F01BEC8F-5554-4738-B200-C86547FE3464}.Release|Any CPU.Build.0 = Release|Any CPU
{D8C856D0-BA32-47A4-A334-71C9A1C66B07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8C856D0-BA32-47A4-A334-71C9A1C66B07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8C856D0-BA32-47A4-A334-71C9A1C66B07}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8C856D0-BA32-47A4-A334-71C9A1C66B07}.Release|Any CPU.Build.0 = Release|Any CPU
{FEB2C12B-7A0F-468C-9063-980FC4A4B8BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FEB2C12B-7A0F-468C-9063-980FC4A4B8BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FEB2C12B-7A0F-468C-9063-980FC4A4B8BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FEB2C12B-7A0F-468C-9063-980FC4A4B8BE}.Release|Any CPU.Build.0 = Release|Any CPU
{168FA1B8-0C0D-4AAE-B026-F1A736027C03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{168FA1B8-0C0D-4AAE-B026-F1A736027C03}.Debug|Any CPU.Build.0 = Debug|Any CPU
{168FA1B8-0C0D-4AAE-B026-F1A736027C03}.Release|Any CPU.ActiveCfg = Release|Any CPU
{168FA1B8-0C0D-4AAE-B026-F1A736027C03}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=3cd064d7_002Df8a3_002D40a1_002D89aa_002Ddd0af673f188/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Test_Indeces" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;TestAncestor&gt;
&lt;TestId&gt;NUnit3x::D8C856D0-BA32-47A4-A334-71C9A1C66B07::net5.0::ln.bson.tests.Tests.Test_Indeces&lt;/TestId&gt;
&lt;/TestAncestor&gt;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>

View File

@ -0,0 +1,361 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using ln.bson.storage.index;
using ln.collections;
using ln.type;
namespace ln.bson.storage
{
public class BsonDocumentStorageConfiguration
{
private bool _sealed;
public string BaseFileName { get; }
private Dictionary<string, Comparison<byte[]>> indeces = new Dictionary<string, Comparison<byte[]>>();
public IEnumerable<KeyValuePair<string, Comparison<byte[]>>> Indeces => indeces;
private Action<string> _logTarget;
public Action<string> LogTarget => _logTarget;
public BsonDocumentStorageConfiguration(string baseFileName)
{
BaseFileName = baseFileName;
_logTarget = (s) => { };
}
public BsonDocumentStorageConfiguration Index(string path) => Index(path, BsonIndex.CompareBytesLittleEndian);
public BsonDocumentStorageConfiguration Index(string path, Comparison<byte[]> comparison)
{
if (_sealed)
throw new ApplicationException();
indeces.Add(path, comparison);
return this;
}
public BsonDocumentStorageConfiguration SetLogTarget(Action<string> logTarget)
{
if (_sealed)
throw new ApplicationException();
_logTarget = logTarget;
return this;
}
internal BsonDocumentStorageConfiguration Seal()
{
_sealed = true;
return this;
}
}
public class BsonDocumentStorage : IEnumerable<BsonDocument>, IDisposable
{
private BsonDocumentStorageConfiguration _configuration;
private ByteArrayFileStorage _fileStorage;
private WeakValueDictionary<long, BsonDocument> _documentCache = new WeakValueDictionary<long, BsonDocument>();
private WeakKeyDictionary<BsonDocument, long> _reverseCache = new WeakKeyDictionary<BsonDocument, long>();
private IndexTree _indexTree = new IndexTree();
public BsonDocumentStorageConfiguration Configuration => _configuration;
public BsonDocumentStorage(string baseFileName) : this(new BsonDocumentStorageConfiguration(baseFileName))
{
}
public BsonDocumentStorage(BsonDocumentStorageConfiguration configuration)
{
_configuration = configuration.Seal();
_fileStorage = new ByteArrayFileStorage(_configuration.BaseFileName);
SetupIndeces();
}
public void Close()
{
lock (this)
{
SaveIndeces();
_fileStorage.Close();
}
}
public BsonDocument LoadDocument(long documentOffset)
{
if (TryLoadDocument(documentOffset, out BsonDocument bsonDocument))
return bsonDocument;
throw new KeyNotFoundException();
}
public bool TryLoadDocument(long documentOffset, out BsonDocument bsonDocument)
{
lock (this)
{
if (TryGetCachedDocument(documentOffset, out bsonDocument))
return true;
if (_fileStorage.TryLoadDocumentBytes(documentOffset, out byte[] documentBytes))
{
bsonDocument = BsonReader.ReadDocument(documentBytes);
TryAddOrUpdateCachedDocument(documentOffset, bsonDocument);
return true;
}
}
return false;
}
/**
* Save Document to Storage
* If bsonDocument originated from this store, replaces this document
* If bsonDocument is unknown to this store, the document is added
*/
public bool SaveDocument(BsonDocument bsonDocument)
{
lock (this)
{
TryGetCachedDocumentOffset(bsonDocument, out long documentOffset);
byte[] documentBytes = bsonDocument.GetBytes();
if (_fileStorage.TrySaveDocumentBytes(documentBytes, out long newDocumentOffset))
{
if (documentOffset != 0)
_documentCache.Remove(documentOffset);
_reverseCache[bsonDocument] = newDocumentOffset;
_documentCache.Add(newDocumentOffset, bsonDocument);
if (documentOffset != 0)
_indexTree.ReplaceDocument(documentOffset, newDocumentOffset, bsonDocument);
else
_indexTree.AddDocument(newDocumentOffset, bsonDocument);
return true;
}
}
return false;
}
/**
* Remove bsonDocument from the storage
* Returns true if document has been successfully removed
* Returns false if document is not known to storage or can't be removed successfully
*/
public bool DeleteDocument(BsonDocument bsonDocument)
{
lock (this)
{
if (TryGetCachedDocumentOffset(bsonDocument, out long documentOffset) &&
_fileStorage.Remove(documentOffset))
{
_documentCache.Remove(documentOffset);
_reverseCache.Remove(bsonDocument);
_indexTree.RemoveDocument(documentOffset);
return true;
}
}
return false;
}
/* Document Cache */
public bool TryGetCachedDocument(long documentOffset, out BsonDocument cachedDocument) =>
_documentCache.TryGetValue(documentOffset, out cachedDocument);
public bool TryGetCachedDocumentOffset(BsonDocument bsonDocument, out long documentOffset) =>
_reverseCache.TryGetValue(bsonDocument, out documentOffset);
public bool TryAddOrUpdateCachedDocument(long documentOffset, BsonDocument bsonDocument)
{
if (_documentCache.TryGetValue(documentOffset, out BsonDocument cachedDocument))
{
_reverseCache.Remove(cachedDocument);
_documentCache.Remove(documentOffset);
}
_documentCache.Add(documentOffset, bsonDocument);
_reverseCache.Add(bsonDocument, documentOffset);
return true;
}
/* End Document Cache */
/* Index methods */
private void SetupIndeces()
{
bool rebuildNeeded = false;
foreach (var indexConfiguration in _configuration.Indeces)
{
if (!_indexTree.EnsureIndex(indexConfiguration.Key, indexConfiguration.Value, out IBsonIndex bsonIndex))
throw new ArgumentOutOfRangeException("duplicate index path");
string indexFileName =
String.Format("{0}.{1}.idx", _configuration.BaseFileName, indexConfiguration.Key);
if (rebuildNeeded || !File.Exists(indexFileName) || !LoadIndexFile(indexFileName, bsonIndex))
rebuildNeeded = true;
}
if (rebuildNeeded)
RebuildIndeces();
}
void RebuildIndeces()
{
_configuration.LogTarget("rebuilding indeces");
_indexTree.Clear();
foreach (BsonDocument bsonDocument in this)
{
if (!_reverseCache.TryGetValue(bsonDocument, out long documentOffset))
throw new ApplicationException("serious bug! no document offset");
_indexTree.AddDocument(documentOffset, bsonDocument);
}
}
private void UpdateIndex(BsonDocument bsonDocument)
{
if (TryGetCachedDocumentOffset(bsonDocument, out long documentOffset))
UpdateIndex(bsonDocument, documentOffset);
else
throw new KeyNotFoundException();
}
private void UpdateIndex(BsonDocument bsonDocument, long documentOffset)
{
_indexTree.AddDocument(documentOffset, bsonDocument);
}
public IEnumerable<BsonDocument> Query(String path, QueryOperator op, BsonValue value)
{
HashSet<long> resultsSet = new HashSet<long>();
_indexTree.Query(path, op, value.GetBytes(), resultsSet);
return new BsonDocumentEnumerable(() => new BsonDocumentEnumerator(this, resultsSet.GetEnumerator()));
}
private void SaveIndeces()
{
_configuration.LogTarget("saving indeces");
foreach (var indexConfiguration in _configuration.Indeces)
{
string indexFileName =
String.Format("{0}.{1}.idx", _configuration.BaseFileName, indexConfiguration.Key);
if (_indexTree.EnsureIndex(indexConfiguration.Key, indexConfiguration.Value, out IBsonIndex bsonIndex))
throw new ArgumentOutOfRangeException("unknown index path");
if (!SaveIndexFile(indexFileName, bsonIndex))
throw new Exception(String.Format("Failed to save index {0} to file {1}", indexConfiguration.Key, indexFileName));
}
}
bool LoadIndexFile(string indexFileName, IBsonIndex index)
{
try
{
using (FileStream indexFile = new FileStream(indexFileName, FileMode.Open, FileAccess.Read))
{
Guid indexGuid = new Guid(indexFile.ReadBytes(16));
if (!indexGuid.Equals(_fileStorage.CurrentGuid))
return false;
while (indexFile.Position < indexFile.Length)
{
long o = indexFile.ReadLong();
int l = indexFile.ReadInteger();
byte[] v = indexFile.ReadBytes(l);
index.AddValue(o, v);
}
}
return true;
}
catch (Exception)
{
return false;
}
}
bool SaveIndexFile(string indexFileName, IBsonIndex index)
{
using (FileStream indexFile = new FileStream(indexFileName, FileMode.Create, FileAccess.Write))
{
indexFile.WriteBytes(_fileStorage.CurrentGuid.ToByteArray());
foreach (var ie in index)
{
indexFile.WriteLong(ie.Key);
indexFile.WriteInteger(ie.Value.Length);
indexFile.WriteBytes(ie.Value);
}
indexFile.Flush();
indexFile.Close();
}
return true;
}
/* End Index methods */
public void Dispose()
{
Close();
_fileStorage?.Dispose();
_fileStorage = null;
}
public class BsonDocumentEnumerable : IEnumerable<BsonDocument>
{
private Func<IEnumerator<BsonDocument>> _getenumerator;
public BsonDocumentEnumerable(Func<IEnumerator<BsonDocument>> getenumerator)
{
_getenumerator = getenumerator;
}
public IEnumerator<BsonDocument> GetEnumerator() => _getenumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public IEnumerator<BsonDocument> GetEnumerator() => new BsonDocumentEnumerator(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
class BsonDocumentEnumerator : IEnumerator<BsonDocument>
{
private BsonDocumentStorage _documentStorage;
private IEnumerator<long> OffsetEnumerator;
public BsonDocumentEnumerator(BsonDocumentStorage documentStorage)
{
_documentStorage = documentStorage;
OffsetEnumerator = _documentStorage._fileStorage.Offsets.GetEnumerator();
}
public BsonDocumentEnumerator(BsonDocumentStorage documentStorage, IEnumerator<long> offsetEnumerator)
{
_documentStorage = documentStorage;
OffsetEnumerator = offsetEnumerator;
}
public bool MoveNext() => OffsetEnumerator.MoveNext();
public void Reset() => OffsetEnumerator.Reset();
public BsonDocument Current => _documentStorage.LoadDocument(OffsetEnumerator.Current);
object IEnumerator.Current => Current;
public void Dispose()
{
OffsetEnumerator?.Dispose();
OffsetEnumerator = null;
}
}
}
}

View File

@ -0,0 +1,86 @@
using System;
using System.Collections;
using System.Collections.Generic;
using ln.bson.mapper;
namespace ln.bson.storage
{
/*
public class BsonFileMapper<T> : IEnumerable<T>,IDisposable
{
public ByteArrayFileStorage FileStorage { get; }
public BsonMapper BsonMapper { get; }
private bool DisposeFileStorage;
public BsonFileMapper(string filename) : this(new ByteArrayFileStorage(filename), BsonMapper.DefaultInstance, true) { }
public BsonFileMapper(string filename, BsonMapper bsonMapper) : this(new ByteArrayFileStorage(filename), bsonMapper, true) { }
public BsonFileMapper(ByteArrayFileStorage byteArrayFileStorage) :this(byteArrayFileStorage, BsonMapper.DefaultInstance, true) { }
public BsonFileMapper(ByteArrayFileStorage byteArrayFileStorage, BsonMapper bsonMapper, bool disposeFileStorage)
{
FileStorage = byteArrayFileStorage;
BsonMapper = bsonMapper;
DisposeFileStorage = disposeFileStorage;
}
public void Save(T element)
{
BsonDocument bsonDocument = BsonMapper.Map(element);
FileStorage.TrySaveDocumentBytes(bsonDocument.GetBytes(), out long documentOffset);
}
class SimpleEnumerator<TT> : IEnumerator<TT>
{
public BsonFileMapper<TT> _fileMapper;
private IEnumerator<BsonDocument> _documentEnumerator;
private TT currentInstance;
public SimpleEnumerator(BsonFileMapper<TT> bsonFileMapper)
{
_fileMapper = bsonFileMapper;
_documentEnumerator = _fileMapper.FileStorage.GetEnumerator();
}
public bool MoveNext()
{
if (_documentEnumerator.MoveNext())
{
currentInstance = BsonMapper.DefaultInstance.Unmap<TT>(_documentEnumerator.Current);
return true;
}
return false;
}
public void Reset()
{
_documentEnumerator.Reset();
currentInstance = default(TT);
}
public TT Current => currentInstance;
object IEnumerator.Current => Current;
public void Dispose()
{
_documentEnumerator?.Dispose();
_documentEnumerator = null;
}
}
public IEnumerator<T> GetEnumerator() => new SimpleEnumerator<T>(this);
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
FileStorage?.Dispose();
}
}
*/
}

View File

@ -0,0 +1,348 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using ln.type;
namespace ln.bson.storage
{
public class ByteArrayFileStorage : IDisposable, IEnumerable<KeyValuePair<long,byte[]>>
{
private string _baseFileName;
private string _offsetFileName;
private FileStream _fileStream;
private SortedSet<long> _storageOffsets = new SortedSet<long>();
public Guid CurrentGuid { get; private set; }
public ByteArrayFileStorage(string baseFilename) : this(baseFilename, baseFilename + ".offset")
{
}
public ByteArrayFileStorage(string baseFilename, string offsetFileName)
{
_baseFileName = baseFilename;
_offsetFileName = offsetFileName;
_fileStream = new FileStream(baseFilename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
_fileStream.Position = 0;
if (_fileStream.Length == 0)
{
CurrentGuid = Guid.NewGuid();
_fileStream.Write(CurrentGuid.ToByteArray());
}
else
{
CurrentGuid = new Guid(_fileStream.ReadBytes(16));
}
if (File.Exists(_offsetFileName))
{
using (FileStream _offsetStream = new FileStream(offsetFileName, FileMode.Open, FileAccess.Read))
{
Guid offsetGuid = new Guid(_offsetStream.ReadBytes(16));
if (offsetGuid.Equals(CurrentGuid))
{
byte[] offsets = _offsetStream.ReadToEnd();
if ((offsets.Length % 8) != 0)
throw new InvalidDataException("offsetfile has invalid length");
int n = 0;
while (n < offsets.Length)
{
_storageOffsets.Add(BitConverter.ToInt64(offsets, n));
n += 8;
}
}
}
}
if (_storageOffsets.Count == 0)
ScanOffsets();
_fileStream.Position = 0;
_fileStream.WriteBytes(Guid.Empty.ToByteArray());
_fileStream.Flush();
}
void ScanOffsets()
{
long currentStorageOffset = 16;
int currentStorageSize = 0;
while (currentStorageOffset < _fileStream.Length)
{
_fileStream.Position = currentStorageOffset;
currentStorageSize = _fileStream.ReadInteger();
if (currentStorageSize < 0)
{
_storageOffsets.Add(-currentStorageOffset);
currentStorageOffset -= currentStorageSize;
}
else
{
_storageOffsets.Add(currentStorageOffset);
currentStorageOffset += currentStorageSize;
}
currentStorageOffset += 4;
}
}
void SaveOffsets()
{
byte[] offsets = new byte[_storageOffsets.Count * 8];
int n = 0;
foreach (long storageOffset in _storageOffsets)
{
Array.Copy(BitConverter.GetBytes(storageOffset), 0, offsets, n * 8, 8);
n++;
}
using (FileStream _offsetStream = new FileStream(_offsetFileName, FileMode.Create, FileAccess.Write))
{
_offsetStream.WriteBytes(CurrentGuid.ToByteArray());
_offsetStream.WriteBytes(offsets);
}
}
public bool TryLoadDocumentBytes(long documentOffset, out byte[] documentBytes)
{
lock (this)
{
if (!_storageOffsets.Contains(documentOffset))
throw new IOException();
_fileStream.Position = documentOffset;
int storageSize = _fileStream.ReadInteger();
if (storageSize < 0)
{
documentBytes = null;
return false;
}
else
{
int documentSize = _fileStream.ReadInteger();
documentBytes = new byte[documentSize];
int nRead = _fileStream.Read(documentBytes, 0, documentSize);
if (nRead != documentSize)
throw new IOException();
return true;
}
}
}
public bool TrySaveDocumentBytes(byte[] documentBytes, out long documentOffset)
{
lock (this)
{
foreach (long currentDocumentOffset in _storageOffsets.GetViewBetween(long.MinValue, 0).Reverse())
{
_fileStream.Position = -currentDocumentOffset;
int currentStorageSize = -_fileStream.ReadInteger();
if (currentStorageSize >= documentBytes.Length + 4)
{
_fileStream.Position = -currentDocumentOffset;
_fileStream.WriteInteger(
currentStorageSize > (documentBytes.Length + 16)
? documentBytes.Length + 4
: currentStorageSize
);
documentOffset = -currentDocumentOffset;
_fileStream.WriteInteger(documentBytes.Length);
_fileStream.Write(documentBytes, 0, documentBytes.Length);
CurrentGuid = Guid.NewGuid();
_storageOffsets.Remove(currentDocumentOffset);
_storageOffsets.Add(documentOffset);
if (currentStorageSize > (documentBytes.Length + 16))
{
long nextOffset = documentOffset + documentBytes.Length + 4;
int nextSize = currentStorageSize - documentBytes.Length - 8;
_fileStream.Position = nextOffset;
_fileStream.WriteInteger(-nextSize);
long testOffset = nextOffset + nextSize + 4;
if ((testOffset != _fileStream.Length) &&
!_storageOffsets.Contains(-testOffset) &&
!_storageOffsets.Contains(testOffset))
throw new IOException("testOffset failed");
_storageOffsets.Add(-nextOffset);
}
_fileStream.Flush();
return true;
}
}
documentOffset = _fileStream.Length;
_fileStream.Position = _fileStream.Length;
_fileStream.WriteInteger(documentBytes.Length + 4);
_fileStream.WriteInteger(documentBytes.Length);
_fileStream.Write(documentBytes, 0, documentBytes.Length);
CurrentGuid = Guid.NewGuid();
_fileStream.Flush();
_storageOffsets.Add(documentOffset);
return true;
}
}
public bool Remove(long documentOffset)
{
lock (this)
{
if (!_storageOffsets.Contains(documentOffset))
throw new IOException();
_fileStream.Position = documentOffset;
int currentStorageSize = _fileStream.ReadInteger();
if (currentStorageSize > 0)
{
currentStorageSize = -currentStorageSize;
_fileStream.Position = documentOffset;
_fileStream.WriteInteger(currentStorageSize);
CurrentGuid = Guid.NewGuid();
_storageOffsets.Remove(documentOffset);
_storageOffsets.Add(-documentOffset);
return true;
}
}
return false;
}
public IEnumerable<long> Offsets => _storageOffsets.GetViewBetween(0,long.MaxValue);
public void Close()
{
if (_fileStream == null)
throw new ObjectDisposedException(nameof(ByteArrayFileStorage));
SaveOffsets();
_fileStream.Position = 0;
_fileStream.WriteBytes(CurrentGuid.ToByteArray());
_fileStream.Flush();
_fileStream.Close();
_fileStream = null;
}
class SimpleDocumentOffsetEnumerator : IEnumerator<KeyValuePair<long,byte[]>>
{
private ByteArrayFileStorage _fileStorage;
private IEnumerator<long> _offsetEnumerator;
private byte[] currentDocument;
public SimpleDocumentOffsetEnumerator(ByteArrayFileStorage fileStorage)
{
this._fileStorage = fileStorage;
}
public bool MoveNext()
{
if (_offsetEnumerator is null)
_offsetEnumerator = _fileStorage.Offsets.GetEnumerator();
if (_offsetEnumerator.MoveNext())
{
if (_fileStorage.TryLoadDocumentBytes(_offsetEnumerator.Current, out currentDocument))
return true;
}
return false;
}
public void Reset()
{
_offsetEnumerator?.Dispose();
_offsetEnumerator = null;
currentDocument = null;
}
public KeyValuePair<long, byte[]> Current =>
new KeyValuePair<long, byte[]>(_offsetEnumerator.Current, currentDocument);
object IEnumerator.Current => Current;
public void Dispose()
{
currentDocument = null;
}
}
/*
class SimpleDocumentEnumerator : IEnumerator<BsonDocument>
{
private SimpleDocumentOffsetEnumerator _offsetEnumerator;
public SimpleDocumentEnumerator(ByteArrayFileStorage fileStorage)
{
_offsetEnumerator = new SimpleDocumentOffsetEnumerator(fileStorage);
}
public bool MoveNext() => _offsetEnumerator.MoveNext();
public void Reset() => _offsetEnumerator.Reset();
public BsonDocument Current => _offsetEnumerator.Current.Value;
object IEnumerator.Current => Current;
public void Dispose()
{
_offsetEnumerator?.Dispose();
}
}
*/
public void Dispose()
{
if (_fileStream != null)
Close();
_fileStream?.Dispose();
_fileStream = null;
}
public IEnumerator<KeyValuePair<long, byte[]>> GetEnumerator() => new SimpleDocumentOffsetEnumerator(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/*
bool TryGetCachedDocument(long documentOffset, out BsonDocument bsonDocument)
{
if (_documentsCache.TryGetValue(documentOffset, out WeakReference<BsonDocument> weakDocument))
{
if (weakDocument.TryGetTarget(out bsonDocument))
return true;
_documentsCache.Remove(documentOffset);
}
bsonDocument = null;
return false;
}
public bool TryLoadDocument(long documentOffset, out BsonDocument bsonDocument)
{
lock (this)
{
if (!_documentOffsets.Contains(documentOffset))
throw new IOException();
if (TryGetCachedDocument(documentOffset, out bsonDocument))
return true;
if (TryLoadDocumentBytes(documentOffset, out int documentSize, out byte[] documentBytes))
{
bsonDocument = BsonReader.ReadDocument(documentBytes, 0, documentSize);
bsonDocument.StorageTag = documentOffset;
_documentsCache.Add(documentOffset, new WeakReference<BsonDocument>(bsonDocument));
return true;
}
}
return false;
}
*/
}

View File

@ -0,0 +1,9 @@
namespace ln.bson.storage
{
public enum CompareEndianess
{
LINEAR,
LITTLE,
BIG
}
}

View File

@ -0,0 +1,135 @@
using System;
using System.Collections;
using System.Collections.Generic;
using ln.bson.mapper;
namespace ln.bson.storage
{
/*
public class MapperSession<T> : IEnumerable<T>, IDisposable
{
private ByteArrayFileStorage _byteArrayFileStorage;
public BsonMapper BsonMapper { get; }
private Dictionary<long, T> instanceCache = new Dictionary<long, T>();
private Dictionary<T, BsonDocument> lookupCache = new Dictionary<T, BsonDocument>();
public MapperSession(ByteArrayFileStorage byteArrayFileStorage)
{
_byteArrayFileStorage = byteArrayFileStorage;
BsonMapper = BsonMapper.DefaultInstance;
}
bool GetInstance(BsonDocument bsonDocument, out T instance)
{
if (!instanceCache.TryGetValue(bsonDocument.StorageTag, out instance))
{
instance = BsonMapper.Unmap<T>(bsonDocument);
instanceCache.Add(bsonDocument.StorageTag, instance);
lookupCache.Add(instance, bsonDocument);
}
return true;
}
public bool Save(T instance)
{
BsonDocument newBsonDocument = BsonMapper.Map(instance);
BsonDocument oldBsonDocument = null;
long documentOffset;
if (lookupCache.TryGetValue(instance, out oldBsonDocument))
{
if (oldBsonDocument.Equals(newBsonDocument))
return true;
if (_byteArrayFileStorage.TrySaveDocumentBytes(newBsonDocument.GetBytes(), out documentOffset))
{
newBsonDocument.StorageTag = documentOffset;
instanceCache.Remove(oldBsonDocument.StorageTag);
instanceCache.Add(documentOffset, instance);
lookupCache.Remove(instance);
lookupCache.Add(instance, newBsonDocument);
return _byteArrayFileStorage.Remove(oldBsonDocument.StorageTag);
}
return false;
}
if (_byteArrayFileStorage.TrySaveDocumentBytes(newBsonDocument.GetBytes(), out documentOffset))
{
instanceCache.Add(documentOffset, instance);
lookupCache.Add(instance, newBsonDocument);
return true;
}
return false;
}
public bool Delete(T instance)
{
if (!lookupCache.TryGetValue(instance, out BsonDocument bsonDocument))
return true;
if (_byteArrayFileStorage.Remove(bsonDocument.StorageTag))
{
lookupCache.Remove(instance);
instanceCache.Remove(bsonDocument.StorageTag);
return true;
}
return false;
}
public void Dispose()
{
instanceCache?.Clear();
instanceCache = null;
lookupCache?.Clear();
lookupCache = null;
_byteArrayFileStorage = null;
}
public class Enumerator : IEnumerator<T>
{
private MapperSession<T> MapperSession;
private IEnumerator<BsonDocument> StorageIterator;
private T currentInstance;
public Enumerator(MapperSession<T> mappingSession)
{
MapperSession = mappingSession;
StorageIterator = MapperSession._byteArrayFileStorage.GetEnumerator();
}
public bool MoveNext()
{
if (StorageIterator.MoveNext())
{
if (!MapperSession.GetInstance(StorageIterator.Current, out currentInstance))
throw new Exception();
return true;
}
return false;
}
public void Reset()
{
currentInstance = default(T);
StorageIterator.Reset();
}
public T Current => currentInstance;
object IEnumerator.Current => Current;
public void Dispose()
{
Reset();
MapperSession = null;
}
}
public IEnumerator<T> GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
*/
}

View File

@ -0,0 +1,14 @@
using System;
namespace ln.bson.storage
{
[Flags]
public enum QueryOperator
{
NOT = (1<<0),
EQUAL = (1<<1),
LESS = (1<<2),
GREATER = (1<<3),
IN = (1 << 4)
}
}

View File

@ -0,0 +1,155 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using ln.collections;
namespace ln.bson.storage.index
{
public delegate int BsonCompareDelegate(byte[] a, byte[] b);
public class BsonIndex : IBsonIndex
{
private BTreeValueSet<byte[], long> _lookup;
private BTreeValueList<long, byte[]> _reverse = new BTreeValueList<long, byte[]>();
public BsonIndex() : this(CompareBytesLittleEndian) { }
public BsonIndex(Comparison<byte[]> compareDelegate)
{
_lookup = new BTreeValueSet<byte[], long>(compareDelegate);
}
public void AddValue(long key, BsonValue bsonValue) => AddValue(key, bsonValue.GetBytes());
public void AddValue(long key, byte[] value)
{
if (_reverse.TryAdd(key, value))
{
if (_lookup.TryAdd(value, key))
return;
_reverse.TryRemove(key, value);
}
throw new ArgumentOutOfRangeException();
}
public void RemoveValue(long key)
{
if (_reverse.TryGet(key, out IEnumerable<byte[]> values))
{
foreach (var value in values)
{
if (!_lookup.TryRemove(value, key))
throw new InvalidOperationException();
}
_reverse.TryRemove(key);
return;
}
throw new ArgumentOutOfRangeException();
}
public void ReplaceValue(long oldKey, long newKey, BsonValue newValue)
{
AddValue(newKey, newValue);
RemoveValue(oldKey);
}
public void Clear()
{
_lookup.Clear();
_reverse.Clear();
}
public void Query(QueryOperator queryOperator, byte[] value, ISet<long> resultSet)
{
IEnumerable<KeyValuePair<byte[], long>> enumeration;
switch (queryOperator)
{
case QueryOperator.EQUAL:
enumeration = _lookup.GetInterval(value, value);
break;
case QueryOperator.EQUAL | QueryOperator.LESS:
enumeration = _lookup.GetInterval(null, value);
break;
case QueryOperator.EQUAL | QueryOperator.GREATER:
enumeration = _lookup.GetInterval(value, null);
break;
case QueryOperator.LESS:
enumeration = _lookup.GetInterval(null, value, (k,v)=>_lookup.Comparison(k,value) < 0);
break;
case QueryOperator.GREATER:
enumeration = _lookup.GetInterval(value, null, (k,v)=>_lookup.Comparison(k,value) > 0);
break;
case QueryOperator.IN:
// ToDo: Implementation
throw new NotSupportedException();
default:
throw new NotSupportedException();
}
resultSet.UnionWith(enumeration.Select(x => x.Value));
}
public static int CompareBytesBigEndian(byte[] a, byte[] b)
{
int cntBytes = a.Length > b.Length ? a.Length : b.Length;
int pa = a.Length - cntBytes;
int pb = b.Length - cntBytes;
while ((pa < a.Length) || (pb < b.Length))
{
pa++;
pb++;
int va = (pa < 0) ? 0 : a[pa];
int vb = (pb < 0) ? 0 : b[pb];
int d = va - vb;
if (d != 0)
return d;
}
return 0;
}
public static int CompareBytesLittleEndian(byte[] a, byte[] b)
{
int cntBytes = a.Length > b.Length ? a.Length : b.Length;
int pa = a.Length;
int pb = b.Length;
while ((pa > 0) || (pb > 0))
{
pa--;
pb--;
int va = (pa >= a.Length) ? 0 : a[pa];
int vb = (pb >= b.Length) ? 0 : b[pb];
int d = va - vb;
if (d != 0)
return d;
}
return 0;
}
public static int CompareBytesLinear(byte[] a, byte[] b)
{
int cntBytes = a.Length > b.Length ? a.Length : b.Length;
int p = 0;
while (p < cntBytes)
{
int va = (p >= a.Length) ? -1 : a[p];
int vb = (p >= b.Length) ? -1 : b[p];
int d = va - vb;
if (d != 0)
return d;
p++;
}
return 0;
}
public IEnumerator<KeyValuePair<long, byte[]>> GetEnumerator() => _reverse.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@ -0,0 +1,17 @@
using System.Collections;
using System.Collections.Generic;
namespace ln.bson.storage.index
{
public interface IBsonIndex : IEnumerable<KeyValuePair<long,byte[]>>
{
void Query(QueryOperator queryOperator, byte[] value, ISet<long> resultSet);
public void AddValue(long key, BsonValue bsonValue);
public void AddValue(long key, byte[] value);
public void RemoveValue(long key);
public void ReplaceValue(long oldKey, long newKey, BsonValue newValue);
public void Clear();
}
}

View File

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Threading;
namespace ln.bson.storage.index
{
public class IndexTree
{
private IndexLeaf _root = new IndexLeaf();
public void Query(string path, QueryOperator queryOperator, byte[] value, ISet<long> resultSet) => Query(path.Split('.'), queryOperator, value, resultSet);
public void Query(Span<string> path, QueryOperator queryOperator, byte[] value, ISet<long> resultSet)
{
if (!_root.TryGetLeaf(path, out IndexLeaf indexLeaf))
throw new KeyNotFoundException();
indexLeaf.Index.Query(queryOperator, value, resultSet);
}
public void AddDocument(long key, BsonDocument bsonDocument) => _root.AddValue(key, bsonDocument);
public void RemoveDocument(long key) => _root.RemoveValue(key);
public void ReplaceDocument(long oldKey, long newKey, BsonDocument newDocument) =>
_root.ReplaceValue(oldKey, newKey, newDocument);
public bool EnsureIndex(string path, Comparison<byte[]> comparison) =>
EnsureIndex(path, comparison, out IBsonIndex bsonIndex);
public bool EnsureIndex(string path, Comparison<byte[]> comparison, out IBsonIndex bsonIndex)
{
_root.EnsureLeaf(path.Split('.',StringSplitOptions.None), out IndexLeaf indexLeaf);
return indexLeaf.EnsureIndex(comparison, out bsonIndex);
}
public void Clear() => _root.Clear();
class IndexLeaf
{
private Dictionary<string, IndexLeaf> _children = new Dictionary<string, IndexLeaf>();
private IBsonIndex _index;
public IBsonIndex Index => _index;
public void EnsureLeaf(Span<string> path, out IndexLeaf indexLeaf)
{
if (path.IsEmpty)
indexLeaf = this;
else
{
if (!_children.TryGetValue(path[0], out IndexLeaf childLeaf))
{
childLeaf = new IndexLeaf();
_children.Add(path[0], childLeaf);
}
childLeaf.EnsureLeaf(path.Slice(1), out indexLeaf);
}
}
public bool TryGetLeaf(Span<string> path, out IndexLeaf indexLeaf)
{
if (path.IsEmpty)
{
indexLeaf = this;
return true;
}
else
{
if (!_children.TryGetValue(path[0], out IndexLeaf childLeaf))
{
indexLeaf = null;
return false;
}
return childLeaf.TryGetLeaf(path.Slice(1), out indexLeaf);
}
}
public bool EnsureIndex() => EnsureIndex(BsonIndex.CompareBytesLittleEndian, out IBsonIndex bsonIndex);
public bool EnsureIndex(Comparison<byte[]> comparison, out IBsonIndex bsonIndex)
{
if (_index is null)
{
bsonIndex = _index = new BsonIndex(comparison);
return true;
}
bsonIndex = _index;
return false;
}
public void AddValue(long key, BsonValue bsonValue)
{
if (_index is not null)
_index.AddValue(key, bsonValue);
if (bsonValue is BsonDocument bsonDocument)
{
foreach (KeyValuePair<string, IndexLeaf> child in _children)
{
if (bsonDocument.Contains(child.Key))
child.Value.AddValue(key, bsonDocument[child.Key]);
}
}
}
public void RemoveValue(long key)
{
if (_index is not null)
_index.RemoveValue(key);
foreach (KeyValuePair<string, IndexLeaf> child in _children)
child.Value.RemoveValue(key);
}
public void ReplaceValue(long oldKey, long newKey, BsonValue newValue)
{
if (_index is not null)
_index.ReplaceValue(oldKey, newKey, newValue);
if (newValue is BsonDocument newDocument)
{
foreach (KeyValuePair<string, IndexLeaf> child in _children)
{
if (newDocument.Contains(child.Key))
child.Value.ReplaceValue(oldKey, newKey, newDocument[child.Key]);
else
child.Value.RemoveValue(oldKey);
}
}
else
{
foreach (KeyValuePair<string, IndexLeaf> child in _children)
child.Value.RemoveValue(oldKey);
}
}
public void Clear()
{
if (_index is not null)
_index.Clear();
foreach (KeyValuePair<string, IndexLeaf> child in _children)
child.Value.Clear();
}
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\ln.collections\ln.collections\ln.collections.csproj" />
<ProjectReference Include="..\ln.bson\ln.bson.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ln.bson" Version="1.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
namespace ln.bson.tests
{
public class Document
{
public Guid UniqueId { get; set; }
public string Title { get; set; }
public string FileName { get; set; }
public string Description { get; set; }
public Guid FolderId { get; set; }
public DateTime Created { get; set; }
public string ContentType { get; set; }
public Dictionary<string, DocumentContent> DocumentContents { get; set; } = new Dictionary<string, DocumentContent>();
public Dictionary<string, string> Keywords { get; set; } = new Dictionary<string, string>();
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace ln.bson.tests
{
public class DocumentContent
{
public bool Authentic { get; set; }
public DateTime Created { get; set; }
public byte[] ContentHash { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
namespace ln.bson.tests
{
public class DocumentFolder
{
public Guid UniqueId { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public Guid ParentId { get; set; }
internal List<DocumentFolder> _children { get; } = new List<DocumentFolder>();
public DocumentFolder[] Children => _children.ToArray();
}
}

View File

@ -0,0 +1,272 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using ln.bson.mapper;
using ln.bson.storage;
using Newtonsoft.Json;
using NUnit.Framework;
namespace ln.bson.tests
{
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test_GetBytes()
{
BsonDocument bsonDocument = new BsonDocument();
bsonDocument["alpha"] = new BsonString("Hallo Welt");
bsonDocument["beta"] = new BsonString("");
bsonDocument["gamma"] = new BsonInt32(int.MaxValue);
bsonDocument["delta"] = new BsonDateTime(DateTime.Now);
BsonDocument embeddedDocument = new BsonDocument();
bsonDocument["zeta"] = embeddedDocument;
BsonArray bsonArray = new BsonArray();
bsonArray.Add(bsonDocument["alpha"]);
bsonArray.Add(bsonDocument["beta"]);
bsonArray.Add(bsonDocument["gamma"]);
bsonDocument["eta"] = bsonArray;
embeddedDocument["alpha"] = bsonArray;
bsonDocument["phi"] = new BsonGuid(Guid.NewGuid());
byte[] documentBytes = bsonDocument.GetBytes();
BsonDocument compareDocument = BsonReader.ReadDocument(documentBytes);
byte[] compareBytes = compareDocument.GetBytes();
Assert.AreEqual(documentBytes, compareBytes);
Assert.Pass();
}
[Test]
public void Test_Mapper()
{
BsonMapping bsonMapping = BsonMapper.DefaultInstance.CreateMapping<MapperTestA>();
Assert.NotNull(bsonMapping);
MapperTestA testA = new MapperTestA(true) { text = "jetzt bin ich ein anderer text" };
BsonDocument mappedDocument = BsonMapper.DefaultInstance.Map(testA);
MapperTestA testA2 = BsonMapper.DefaultInstance.Unmap<MapperTestA>(mappedDocument);
FieldsAreEqual(testA, testA2);
Document document = new Document();
document.Keywords.Add("supplier", "123-4567-890-AAA");
document.Keywords.Add("DocType", "supplier-invoice");
//document.DocumentContents.Add("application/pdf", new DocumentContent(){ Authentic = true, Created = DateTime.Now });
BsonDocument bsonDocument = BsonMapper.DefaultInstance.Map(document);
Document unmappedDocument = BsonMapper.DefaultInstance.Unmap<Document>(bsonDocument);
FieldsAreEqual(document, unmappedDocument);
Assert.Pass();
}
[Test]
public void Test_FileStorage()
{
BsonDocument[] bsonDocuments = PrepareDocuments(256);
int n = 0;
File.Delete("test1.bson");
using (ByteArrayFileStorage byteArrayFileStorage = new ByteArrayFileStorage("test1.bson"))
{
foreach (BsonDocument bsonDocument in bsonDocuments)
Assert.IsTrue(byteArrayFileStorage.TrySaveDocumentBytes(bsonDocument.GetBytes(), out long documentOffset));
foreach (KeyValuePair<long,byte[]> storageData in byteArrayFileStorage)
{
Assert.AreEqual(bsonDocuments[n].GetBytes(), storageData.Value);
n++;
}
n = 0;
foreach (KeyValuePair<long,byte[]> storageData in byteArrayFileStorage.ToArray())
{
Assert.IsTrue(byteArrayFileStorage.Remove(storageData.Key));
n++;
if (n >= 64)
break;
}
n = 64;
foreach (KeyValuePair<long,byte[]> storageData in byteArrayFileStorage)
{
Assert.AreEqual(bsonDocuments[n].GetBytes(), storageData.Value);
n++;
}
}
}
[Test]
public void Test_DocumentStorage()
{
BsonDocument[] bsonDocuments = PrepareDocuments(256);
int n = 0;
File.Delete("documents.bson");
using (BsonDocumentStorage storage = new BsonDocumentStorage(new BsonDocumentStorageConfiguration("documents.bson")))
{
foreach (BsonDocument bsonDocument in bsonDocuments)
Assert.IsTrue(storage.SaveDocument(BsonReader.ReadDocument(bsonDocument.GetBytes())));
foreach (BsonDocument bsonDocument in storage)
{
Assert.AreEqual(bsonDocuments[n], bsonDocument);
Assert.AreNotSame(bsonDocuments[n], bsonDocument);
n++;
}
n = 0;
foreach (BsonDocument bsonDocument in storage.ToArray())
{
Assert.IsTrue(storage.DeleteDocument(bsonDocument));
n++;
if (n >= 64)
break;
}
n = 64;
foreach (BsonDocument bsonDocument in storage)
{
Assert.AreEqual(bsonDocuments[n], bsonDocument);
Assert.AreNotSame(bsonDocuments[n], bsonDocument);
n++;
}
}
}
/*
[Test]
public void Test_FileMapper()
{
Document[] documents = new Document[64];
for (int n = 0; n < documents.Length; n++)
documents[n] = new Document()
{ UniqueId = Guid.NewGuid(), FolderId = Guid.NewGuid(), FileName = string.Format("{0}.txt", n) };
File.Delete("test.documents.bson");
using (BsonFileMapper<Document> fileMapper = new BsonFileMapper<Document>("test.documents.bson"))
{
foreach (var document in documents)
fileMapper.Save(document);
int n = 0;
foreach (Document document in fileMapper)
{
Assert.AreEqual(documents[n].UniqueId, document.UniqueId);
Assert.AreEqual(documents[n].FolderId, document.FolderId);
Assert.AreEqual(documents[n].FileName, document.FileName);
n++;
}
}
}
*/
[Test]
public void Test_Indeces()
{
BsonDocument[] bsonDocuments = PrepareDocuments(256);
int n = 0;
File.Delete("documents.bson");
using (BsonDocumentStorage storage = new BsonDocumentStorage(
new BsonDocumentStorageConfiguration("documents.bson")
.Index("__NO__")
.SetLogTarget(Console.WriteLine)
))
{
foreach (BsonDocument bsonDocument in bsonDocuments)
Assert.IsTrue(storage.SaveDocument(BsonReader.ReadDocument(bsonDocument.GetBytes())));
}
using (BsonDocumentStorage storage = new BsonDocumentStorage(
new BsonDocumentStorageConfiguration("documents.bson")
.Index("__NO__")
.SetLogTarget(Console.WriteLine)
))
{
var e = storage.Query("__NO__", QueryOperator.LESS, 10);
Assert.AreEqual(
10,
e.Count()
);
}
}
public BsonDocument[] PrepareDocuments(int count)
{
BsonDocument[] documents = new BsonDocument[count];
for (int n = 0; n < documents.Length; n++)
{
documents[n] = new BsonDocument();
documents[n]["__NO__"] = new BsonInt32(n);
documents[n]["__UUID__"] = Guid.NewGuid();
}
return documents;
}
public static void FieldsAreEqual<T>(T a, T b)
{
foreach (FieldInfo fieldInfo in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic))
{
if (fieldInfo.FieldType.IsAssignableTo(typeof(ICollection)))
CollectionAssert.AreEquivalent((ICollection)fieldInfo.GetValue(a), (ICollection)fieldInfo.GetValue(b));
else
Assert.AreEqual(fieldInfo.GetValue(a), fieldInfo.GetValue(b));
}
}
class MapperTestA
{
public string text = "ich bin ein text";
public int i32 = Int32.MinValue;
public long i64 = Int64.MaxValue;
public double d64 = Double.MaxValue;
public Dictionary<string, string> keywords = new Dictionary<string, string>();
public MapperTestA(){}
public MapperTestA(bool populateKeywords)
{
if (populateKeywords)
{
keywords.Add("keyword1", "some-value");
keywords.Add("keyword2", "another-value");
keywords.Add("keyword3", "some text that you would expect to be part of good test scenario");
}
}
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="coverlet.collector" Version="3.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ln.bson.storage\ln.bson.storage.csproj" />
<ProjectReference Include="..\ln.bson\ln.bson.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,184 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Text;
using System.Text.Json;
namespace ln.bson
{
public class BsonDocument : BsonValue, IEnumerable<KeyValuePair<string, BsonValue>>
{
private Dictionary<string, BsonValue> _values = new Dictionary<string, BsonValue>();
public long StorageTag { get; set; }
public object UserTag { get; set; }
public BsonDocument():base(BsonType.Document){}
public override object ToNative()
{
Dictionary<string, object> native = new Dictionary<string, object>();
foreach (var bsonValue in _values)
native.Add(bsonValue.Key, bsonValue.Value.ToNative());
return native;
}
public override byte[] GetBytes()
{
byte[][] names = new byte[_values.Count][];
byte[][] bvalues = new byte[_values.Count][];
BsonType[] bsonTypes = new BsonType[_values.Count];
int n = 0;
int totalBytes = 0;
foreach (var bsonValue in _values)
{
bsonTypes[n] = bsonValue.Value.BsonType;
names[n] = Encoding.UTF8.GetBytes(bsonValue.Key);
bvalues[n] = bsonValue.Value.GetBytes();
totalBytes += names[n].Length + bvalues[n].Length;
n++;
}
byte[] containerBytes = new byte[totalBytes + 5 + (_values.Count << 1 )];
BitConverter.TryWriteBytes(containerBytes, containerBytes.Length);
int offset = 4;
for (n = 0; n < names.Length; n++)
{
containerBytes[offset] = (byte)bsonTypes[n];
Array.Copy(names[n], 0, containerBytes, offset + 1, names[n].Length);
Array.Copy(bvalues[n], 0, containerBytes, offset + 2 + names[n].Length, bvalues[n].Length);
offset += 2 + names[n].Length + bvalues[n].Length;
}
return containerBytes;
}
public BsonValue this[string name]
{
get => _values[name];
set => _values[name] = value;
}
public void Add(string name, BsonValue bsonValue) => _values.Add(name, bsonValue);
public void Remove(string name) => _values.Remove(name);
public bool Contains(string name) => _values.ContainsKey(name);
public IEnumerable<string> Keys => _values.Keys;
public IEnumerable<BsonValue> Values => _values.Values;
public IEnumerator<KeyValuePair<string, BsonValue>> GetEnumerator()
{
return _values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_values).GetEnumerator();
}
public override bool Equals(object? obj)
{
if (obj is BsonDocument other)
{
if (_values.Count == other._values.Count)
{
foreach (String key in _values.Keys)
{
if (!other._values.ContainsKey(key) || !_values[key].Equals(other._values[key]))
return false;
}
return true;
}
}
return false;
}
public override bool TryGetMember(GetMemberBinder binder, out object? result)
{
if (_values.TryGetValue(binder.Name, out BsonValue bsonValue))
{
result = bsonValue;
return true;
}
return base.TryGetMember(binder, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object? value)
{
Add(binder.Name, (BsonValue)value);
return true;
}
}
public class BsonArray : BsonValue, IEnumerable<BsonValue>
{
private List<BsonValue> _values = new List<BsonValue>();
public BsonArray():base(BsonType.Array){}
public override object ToNative()
{
object[] native = new object[_values.Count];
for (int n = 0; n < native.Length; n++)
native[n] = _values[n].ToNative();
return native;
}
public override byte[] GetBytes()
{
byte[][] names = new byte[_values.Count][];
byte[][] bvalues = new byte[_values.Count][];
BsonType[] bsonTypes = new BsonType[_values.Count];
int n = 0;
int totalBytes = 0;
foreach (var bsonValue in _values)
{
bsonTypes[n] = bsonValue.BsonType;
names[n] = Encoding.UTF8.GetBytes(n.ToString());
bvalues[n] = bsonValue.GetBytes();
totalBytes += names[n].Length + bvalues[n].Length;
n++;
}
byte[] containerBytes = new byte[totalBytes + 5 + (_values.Count << 1)];
BitConverter.TryWriteBytes(containerBytes, totalBytes);
int offset = 4;
for (n = 0; n < names.Length; n++)
{
containerBytes[offset] = (byte)bsonTypes[n];
Array.Copy(names[n], 0, containerBytes, offset + 1, names[n].Length);
Array.Copy(bvalues[n], 0, containerBytes, offset + 2 + names[n].Length, bvalues[n].Length);
offset += 2 + names[n].Length + bvalues[n].Length;
}
return containerBytes;
}
public BsonValue this[int i]
{
get => _values[i];
set => _values[i] = value;
}
public void Add(BsonValue bsonValue) => _values.Add(bsonValue);
public void Remove(BsonValue bsonValue) => _values.Remove(bsonValue);
public void RemoveAt(int i) => _values.RemoveAt(i);
public bool Contains(BsonValue bsonValue) => _values.Contains(bsonValue);
public int Length => _values.Count;
public IEnumerator<BsonValue> GetEnumerator()
{
return _values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_values).GetEnumerator();
}
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.IO;
using System.Text;
using ln.type;
namespace ln.bson
{
public static class BsonReader
{
public static BsonDocument ReadDocument(byte[] buffer) => ReadDocument(buffer, 0, buffer.Length);
public static BsonDocument ReadDocument(byte[] buffer, int offset) => ReadDocument(buffer, offset, buffer.Length - offset);
public static BsonDocument ReadDocument(byte[] buffer, int offset, int length)
{
using (var ms = new MemoryStream(buffer, offset, length))
return ReadDocument(ms);
}
public static BsonDocument ReadDocument(Stream stream)
{
BsonDocument bsonDocument = new BsonDocument();
ReadDocumentItems(stream, ((s, value) => bsonDocument.Add(s,value)));
return bsonDocument;
}
private static BsonArray ReadArray(Stream stream)
{
BsonArray bsonArray = new BsonArray();
ReadDocumentItems(stream, ((s, value) => bsonArray.Add(value)));
return bsonArray;
}
private static void ReadDocumentItems(Stream stream, Action<string, BsonValue> appender)
{
int documentLength = stream.ReadInteger();
BsonType bsonType = BsonType.Null;
while (bsonType != BsonType.EndOfDocument)
{
bsonType = (BsonType)stream.ReadByte();
if (bsonType == BsonType.EndOfDocument)
return;
string name = stream.ReadCString();
BsonValue bsonValue = null;
switch (bsonType)
{
case BsonType.Boolean:
if (stream.ReadByte() == 0)
bsonValue = BsonBoolean.False;
else
bsonValue = BsonBoolean.True;
break;
case BsonType.Double:
bsonValue = new BsonDouble(stream.ReadDouble());
break;
case BsonType.Int32:
bsonValue = new BsonInt32(stream.ReadInteger());
break;
case BsonType.Int64:
bsonValue = new BsonInt64(stream.ReadLong());
break;
case BsonType.Null:
bsonValue = BsonNull.Instance;
break;
case BsonType.Binary:
int blength = stream.ReadInteger();
BsonBinarySubType subType = (BsonBinarySubType)stream.ReadByte();
switch (subType)
{
case BsonBinarySubType.UuidOld:
case BsonBinarySubType.Uuid:
if (blength != 16)
throw new ArgumentOutOfRangeException(nameof(blength));
bsonValue = new BsonGuid(stream.ReadBytes(blength));
break;
default:
bsonValue = new BsonBinary(subType, stream.ReadBytes(blength));
break;
}
break;
case BsonType.String:
blength = stream.ReadInteger();
bsonValue = new BsonString(Encoding.UTF8.GetString(stream.ReadBytes(blength),0, blength-1));
break;
case BsonType.DateTime:
bsonValue = new BsonDateTime(DateTimeExtensions.FromUnixTimeMilliseconds(stream.ReadLong()));
break;
case BsonType.Document:
bsonValue = ReadDocument(stream);
break;
case BsonType.Array:
bsonValue = ReadArray(stream);
break;
case BsonType.EndOfDocument:
return;
default:
throw new NotSupportedException();
}
appender(name, bsonValue);
}
}
}
}

View File

@ -0,0 +1,39 @@
namespace ln.bson
{
public enum BsonType : byte
{
EndOfDocument = 0x00,
Double = 0x01,
String = 0x02,
Document = 0x03,
Array = 0x04,
Binary = 0x05,
ObjectId = 0x07,
Boolean = 0x08,
DateTime = 0x09,
Null = 0x0a,
Regex = 0x0b,
DBPointer = 0x0c,
JavaScript = 0x0d,
Symbol = 0x0e,
ScopedJavaScript = 0x0f,
Int32 = 0x10,
TimeStamp = 0x11,
Int64 = 0x12,
Decimal128 = 0x13,
MinKey = 0xFF,
MaxKey = 0x7F,
}
public enum BsonBinarySubType : byte
{
Generic = 0x00,
Function = 0x01,
Binary = 0x02,
UuidOld = 0x03,
Uuid = 0x04,
MD5 = 0x05,
Encrypted = 0x06,
UserDefined = 0x80,
}
}

View File

@ -0,0 +1,154 @@
using System;
using System.Text;
using ln.type;
namespace ln.bson
{
public class BsonString : BsonValue
{
public string Value { get; set; }
public override object ToNative() => Value;
public BsonString() :base(BsonType.String){}
public BsonString(string value) : this()
{
Value = value;
}
public override byte[] GetBytes()
{
byte[] sbytes = Encoding.UTF8.GetBytes(Value);
byte[] buffer = new byte[sbytes.Length + 5];
Array.Copy(BitConverter.GetBytes(sbytes.Length+1), 0, buffer, 0, 4);
Array.Copy(sbytes, 0, buffer, 4, sbytes.Length);
return buffer;
}
public static implicit operator string(BsonString bsonString) => bsonString.Value;
}
public class BsonDouble : BsonValue
{
public double Value { get; set; }
public override object ToNative() => Value;
public override byte[] GetBytes() => BitConverter.GetBytes(Value);
public BsonDouble():base(BsonType.Double){}
public BsonDouble(double value) : this()
{
Value = value;
}
}
public class BsonBinary : BsonValue
{
public virtual byte[] Value { get; set; }
public BsonBinarySubType SubType { get; set; }
public override object ToNative() => Value;
public BsonBinary() : base(BsonType.Binary) { }
public BsonBinary(BsonBinarySubType subType) : this()
{
SubType = subType;
}
public BsonBinary(BsonBinarySubType subType, byte[] value) : this(subType)
{
Value = value;
}
public override byte[] GetBytes()
{
byte[] b = new byte[Value.Length + 5];
Array.Copy(BitConverter.GetBytes(Value.Length), 0, b, 0, 4);
b[4] = (byte)SubType;
Array.Copy(Value, 0, b, 5, Value.Length);
return b;
}
}
public class BsonGuid : BsonBinary
{
public override byte[] Value { get => GuidValue.ToByteArray(); set => GuidValue = new Guid(value); }
public Guid GuidValue { get; set; }
public override object ToNative() => GuidValue;
public BsonGuid() : this(Guid.Empty) { }
public BsonGuid(Guid guid) : base(BsonBinarySubType.Uuid)
{
GuidValue = guid;
}
public BsonGuid(byte[] guid) : this(new Guid(guid)) { }
}
public class BsonBoolean : BsonValue
{
public static readonly BsonBoolean True = new BsonBoolean(true);
public static readonly BsonBoolean False = new BsonBoolean(false);
public bool Value { get; }
private BsonBoolean(bool b) : base(BsonType.Boolean)
{
Value = b;
}
public override byte[] GetBytes() => new[] { Value ? (byte)0x01 : (byte)0x00 };
public override object ToNative() => Value;
}
public class BsonDateTime : BsonValue
{
public DateTime Value { get; set; }
public override byte[] GetBytes() => BitConverter.GetBytes((long)Value.ToUnixTimeMilliseconds());
public override object ToNative() => Value;
public BsonDateTime(): base(BsonType.DateTime){}
public BsonDateTime(DateTime dateTime) : base(BsonType.DateTime)
{
Value = dateTime;
}
}
public class BsonNull : BsonValue
{
public static readonly BsonNull Instance = new BsonNull();
protected BsonNull(): base(BsonType.Null){}
public override byte[] GetBytes() => new byte[0];
public override object ToNative() => null;
}
public class BsonInt32 : BsonValue
{
public Int32 Value { get; set; }
public override object ToNative() => Value;
public override byte[] GetBytes() => BitConverter.GetBytes(Value);
public BsonInt32():base(BsonType.Int32){}
public BsonInt32(Int32 value) : this()
{
Value = value;
}
}
public class BsonInt64 : BsonValue
{
public Int64 Value { get; set; }
public override object ToNative() => Value;
public override byte[] GetBytes() => BitConverter.GetBytes(Value);
public BsonInt64():base(BsonType.Int64){}
public BsonInt64(Int64 value) : this()
{
Value = value;
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Dynamic;
using System.IO;
namespace ln.bson
{
public abstract class BsonValue : DynamicObject
{
public BsonType BsonType { get; }
protected BsonValue(BsonType bsonType)
{
BsonType = bsonType;
}
public abstract object ToNative();
public abstract Byte[] GetBytes();
public virtual T As<T>() => throw new NotImplementedException();
public virtual void WriteTo(Stream stream) => stream.Write(GetBytes());
public virtual int WriteTo(byte[] buffer, int offset)
{
byte[] vBytes = GetBytes();
Array.Copy(vBytes, 0, buffer, offset, vBytes.Length);
return vBytes.Length;
}
public override bool Equals(object? obj) => (obj is BsonValue bsonValue) && object.Equals(this.ToNative(), bsonValue.ToNative());
public static implicit operator BsonValue(bool b) => b ? BsonBoolean.True : BsonBoolean.False;
public static implicit operator BsonValue(String s) => new BsonString(s);
public static implicit operator BsonValue(double d) => new BsonDouble(d);
public static implicit operator BsonValue(DateTime dt) => new BsonDateTime(dt);
public static implicit operator BsonValue(int i) => new BsonInt32(i);
public static implicit operator BsonValue(long l) => new BsonInt64(l);
public static implicit operator BsonValue(byte[] b) => new BsonBinary(BsonBinarySubType.Generic, b);
public static implicit operator BsonValue(Guid guid) => new BsonGuid(guid);
public static implicit operator string(BsonValue bsonString) => ((BsonString)bsonString).Value;
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ln.type" Version="0.1.9" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using ln.bson.mapper.mappings;
namespace ln.bson.mapper
{
public class BsonMapper
{
private static BsonMapper _defaultInstance;
public static BsonMapper DefaultInstance
{
get
{
if (_defaultInstance is null)
_defaultInstance = new BsonMapper();
return _defaultInstance;
}
}
private Dictionary<Type, BsonMapping> _bsonMappings = new Dictionary<Type, BsonMapping>();
public BsonDocument Map<T>(T o) => Map(typeof(T), o);
public BsonDocument Map(object o) => Map(o.GetType(), o);
public BsonDocument Map(Type type, object o)
{
if (TryGetMapping(type, out BsonMapping bsonMapping))
{
if (bsonMapping.TryMapValue(this, o, out BsonValue bsonValue) &&
(bsonValue is BsonDocument bsonDocument))
return bsonDocument;
throw new FormatException("could not map object to BsonDocument");
}
throw new NotSupportedException();
}
public T Unmap<T>(BsonDocument document) => (T)Unmap(typeof(T), document);
public object Unmap(Type type, BsonDocument document)
{
if (TryGetMapping(type, out BsonMapping bsonMapping) &&
bsonMapping.TryMapValue(this, document, out object o)
)
return o;
return null;
}
public bool TryGetMapping(Type type, out BsonMapping bsonMapping)
{
if (!_defaultMappings.TryGetValue(type, out bsonMapping) && !_bsonMappings.TryGetValue(type, out bsonMapping))
{
if (type.IsArray)
{
Type mappingType = typeof(ArrayMapping<>).MakeGenericType(type.GetElementType());
bsonMapping = (BsonMapping) Activator.CreateInstance(mappingType);
AddMapping(bsonMapping);
} else if (type.IsGenericType &&
type.GetGenericTypeDefinition().Equals(typeof(Dictionary<,>)) &&
type.GetGenericArguments()[0].Equals(typeof(string)))
{
Type mappingType = typeof(DictionaryMapping<>).MakeGenericType(type.GetGenericArguments()[1]);
bsonMapping = (BsonMapping)Activator.CreateInstance(mappingType);
AddMapping(bsonMapping);
} else if (type.IsGenericType &&
type.GetGenericTypeDefinition().Equals(typeof(IList<>)))
{
Type mappingType = typeof(IListMapping<,>).MakeGenericType(type, type.GetGenericArguments()[0]);
bsonMapping = (BsonMapping)Activator.CreateInstance(mappingType);
AddMapping(bsonMapping);
} else if (!type.IsPrimitive)
{
Type mappingType = typeof(ClassStructMapping<>).MakeGenericType(type);
bsonMapping = (BsonMapping) Activator.CreateInstance(mappingType);
AddMapping(bsonMapping);
} else
throw new NotSupportedException();
}
return true;
}
public void AddMapping(BsonMapping bsonMapping) => AddMapping(bsonMapping, false);
public void AddMapping(BsonMapping bsonMapping, bool replace)
{
_bsonMappings.Add(bsonMapping.SourceType, bsonMapping);
}
public BsonMapping CreateMapping<T>()
{
if (TryGetMapping(typeof(T), out BsonMapping mapping))
return mapping;
return null;
}
private static Dictionary<Type, BsonMapping> _defaultMappings = new Dictionary<Type, BsonMapping>();
public static void AddDefaultMapping(BsonMapping bsonMapping) =>
_defaultMappings.Add(bsonMapping.SourceType, bsonMapping);
static BsonMapper()
{
AddDefaultMapping(new BsonMapping.Int32());
AddDefaultMapping(new BsonMapping.Int64());
AddDefaultMapping(new BsonMapping.Double());
AddDefaultMapping(new BsonMapping.String());
AddDefaultMapping(new BsonMapping.Guid());
AddDefaultMapping(new BsonMapping.Boolean());
AddDefaultMapping(new BsonMapping.Binary());
AddDefaultMapping(new BsonMapping.DateTime());
}
}
}

View File

@ -0,0 +1,219 @@
using System;
namespace ln.bson.mapper
{
public abstract class BsonMapping
{
public Type SourceType { get; }
public BsonMapping(Type sourceType)
{
SourceType = sourceType;
}
public abstract bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue);
public abstract bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o);
public class Int32 : BsonMapping
{
public Int32() : base(typeof(int))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
bsonValue = new BsonInt32((int)o);
return true;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonInt32 bsonInt32)
{
o = bsonInt32.Value;
return true;
}
o = null;
return false;
}
}
public class Int64 : BsonMapping
{
public Int64() : base(typeof(long))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
bsonValue = new BsonInt64((long)o);
return true;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonInt64 bsonInt64)
{
o = bsonInt64.Value;
return true;
}
o = null;
return false;
}
}
public class String : BsonMapping
{
public String() : base(typeof(string))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
bsonValue = new BsonString((string)o);
return true;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonString bson)
{
o = bson.Value;
return true;
}
o = null;
return false;
}
}
public class Double : BsonMapping
{
public Double() : base(typeof(double))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
bsonValue = new BsonDouble((double)o);
return true;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonDouble bsonDouble)
{
o = bsonDouble.Value;
return true;
}
o = null;
return false;
}
}
public class DateTime : BsonMapping
{
public DateTime() : base(typeof(System.DateTime))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
bsonValue = new BsonDateTime((System.DateTime)o);
return true;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonDateTime bsonDateTime)
{
o = bsonDateTime.Value;
return true;
}
o = null;
return false;
}
}
public class Boolean : BsonMapping
{
public Boolean() : base(typeof(bool))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
bsonValue = (bool)o ? BsonBoolean.True : BsonBoolean.False;
return true;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonBoolean bson)
{
o = bson.Value;
return true;
}
o = null;
return false;
}
}
public class Guid : BsonMapping
{
public Guid() : base(typeof(System.Guid))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
bsonValue = new BsonGuid((System.Guid)o);
return true;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonGuid bson)
{
o = bson.GuidValue;
return true;
}
o = null;
return false;
}
}
public class Binary : BsonMapping
{
public Binary() : base(typeof(byte[]))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
bsonValue = new BsonBinary(BsonBinarySubType.Generic, (byte[])o);
return true;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonBinary bson)
{
o = bson.Value;
return true;
}
o = null;
return false;
}
}
}
}

View File

@ -0,0 +1,61 @@
using System;
namespace ln.bson.mapper.mappings
{
public class ArrayMapping<T> : BsonMapping
{
public ArrayMapping() : base(typeof(T[]))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
if (mapper.TryGetMapping(typeof(T), out BsonMapping elementMapping))
{
BsonArray bsonArray = new BsonArray();
T[] sourceArray = (T[])o;
foreach (T element in sourceArray)
{
if (!elementMapping.TryMapValue(mapper, element, out BsonValue elementValue))
{
bsonValue = null;
return false;
}
bsonArray.Add(elementValue);
}
bsonValue = bsonArray;
return true;
}
bsonValue = null;
return false;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonArray bsonArray && mapper.TryGetMapping(typeof(T), out BsonMapping elementMapping))
{
T[] destinationArray = new T[bsonArray.Length];
for (int i = 0; i < destinationArray.Length; i++)
{
if (!elementMapping.TryMapValue(mapper, bsonArray[i], out object element))
{
o = null;
return false;
}
destinationArray[i] = (T)element;
}
o = destinationArray;
return true;
}
o = null;
return false;
}
}
}

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace ln.bson.mapper.mappings
{
public class ClassStructMapping<T> : BsonMapping
{
private FieldInfo[] mappedFields;
//private PropertyInfo[] mappedProperties;
public ClassStructMapping() : base(typeof(T))
{
mappedFields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic);
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
BsonDocument bsonDocument = new BsonDocument();
foreach (FieldInfo fieldInfo in mappedFields)
{
object fieldValue = fieldInfo.GetValue(o);
if (fieldValue is null)
bsonDocument.Add(fieldInfo.Name, BsonNull.Instance);
else if (mapper.TryGetMapping(fieldInfo.FieldType, out BsonMapping fieldMapping) &&
fieldMapping.TryMapValue(mapper, fieldValue, out BsonValue bsonElementValue))
bsonDocument.Add(fieldInfo.Name, bsonElementValue);
}
bsonValue = bsonDocument;
return true;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
o = Activator.CreateInstance(SourceType, true);
if (bsonValue is BsonDocument bsonDocument)
{
foreach (FieldInfo fieldInfo in mappedFields)
{
if (bsonDocument.Contains(fieldInfo.Name))
{
object fieldValue;
BsonValue bsonFieldValue = bsonDocument[fieldInfo.Name];
if (bsonFieldValue is BsonNull)
fieldValue = null;
else if (mapper.TryGetMapping(fieldInfo.FieldType, out BsonMapping fieldMapping) &&
fieldMapping.TryMapValue(mapper, bsonDocument[fieldInfo.Name], out fieldValue))
{ }
else
{
o = null;
return false;
}
fieldInfo.SetValue(o, fieldValue);
}
}
}
return true;
}
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace ln.bson.mapper.mappings
{
public class DictionaryMapping<T> : BsonMapping
{
public DictionaryMapping() : base(typeof(Dictionary<string,T>))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
if (mapper.TryGetMapping(typeof(T), out BsonMapping elementMapping))
{
Dictionary<string, T> d = (Dictionary<string, T>)o;
BsonDocument bsonDocument = new BsonDocument();
foreach (KeyValuePair<string, T> keyValuePair in d)
{
if (!elementMapping.TryMapValue(mapper, keyValuePair.Value, out BsonValue elementValue))
{
bsonValue = null;
return false;
}
bsonDocument.Add(keyValuePair.Key, elementValue);
}
bsonValue = bsonDocument;
return true;
}
bsonValue = null;
return false;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonDocument bsonDocument && mapper.TryGetMapping(typeof(T), out BsonMapping elementMapping))
{
Dictionary<string, T> d = new Dictionary<string, T>();
foreach (KeyValuePair<string,BsonValue> keyValuePair in bsonDocument)
{
if (!elementMapping.TryMapValue(mapper, keyValuePair.Value, out object element))
{
o = null;
return false;
}
d.Add(keyValuePair.Key, (T)element);
}
o = d;
return true;
}
o = null;
return false;
}
}
}

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
namespace ln.bson.mapper.mappings
{
public class IListMapping<T, E> : BsonMapping where T : IList<E>
{
public IListMapping() : base(typeof(T))
{
}
public override bool TryMapValue(BsonMapper mapper, object o, out BsonValue bsonValue)
{
if (mapper.TryGetMapping(typeof(E), out BsonMapping elementMapping))
{
BsonArray bsonArray = new BsonArray();
T source = (T)o;
foreach (E element in source)
{
if (!elementMapping.TryMapValue(mapper, element, out BsonValue elementValue))
{
bsonValue = null;
return false;
}
bsonArray.Add(elementValue);
}
bsonValue = bsonArray;
return true;
}
bsonValue = null;
return false;
}
public override bool TryMapValue(BsonMapper mapper, BsonValue bsonValue, out object o)
{
if (bsonValue is BsonArray bsonArray && mapper.TryGetMapping(typeof(T), out BsonMapping elementMapping))
{
T destinationList = Activator.CreateInstance<T>();
for (int i = 0; i < bsonArray.Length; i++)
{
if (!elementMapping.TryMapValue(mapper, bsonArray[i], out object element))
{
o = null;
return false;
}
destinationList.Add((E)element);
}
o = destinationList;
return true;
}
o = null;
return false;
}
}
}