Skip to content

Commit 9048773

Browse files
committed
New option in EventLayer and EventLayer2 to forbid serialization of lazy DB objects like IDictionary<,>
1 parent 1266b40 commit 9048773

8 files changed

+164
-3
lines changed

BTDB/EventStore2Layer/EventSerializer.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,17 @@ public class EventSerializer : IEventSerializer, ITypeDescriptorCallbacks, IDesc
4141
readonly ISymmetricCipher _symmetricCipher;
4242

4343
readonly bool _useInputDescriptors;
44+
readonly bool _forbidSerializationOfLazyDBObjects;
4445
bool _newTypeFound;
4546

4647
public EventSerializer(ITypeNameMapper? typeNameMapper = null,
4748
ITypeConvertorGenerator? typeConvertorGenerator = null, ISymmetricCipher? symmetricCipher = null,
48-
bool useInputDescriptors = true)
49+
bool useInputDescriptors = true, bool forbidSerializationOfLazyDBObjects = false)
4950
{
5051
TypeNameMapper = typeNameMapper ?? new FullNameTypeMapper();
5152
ConvertorGenerator = typeConvertorGenerator ?? DefaultTypeConvertorGenerator.Instance;
5253
_useInputDescriptors = useInputDescriptors;
54+
_forbidSerializationOfLazyDBObjects = forbidSerializationOfLazyDBObjects;
5355
_symmetricCipher = symmetricCipher ?? new InvalidSymmetricCipher();
5456
_id2Info.Reserve(ReservedBuildinTypes + 10);
5557
_id2Info.Add(null); // 0 = null
@@ -329,6 +331,10 @@ public ByteBuffer Serialize(out bool hasMetaData, object? obj)
329331
if (_typeOrDescriptor2InfoNew.TryGetValue(type, out result)) return result.Descriptor;
330332
ITypeDescriptor desc = null;
331333
Type typeAlternative = null;
334+
if (_forbidSerializationOfLazyDBObjects && type.InheritsOrImplements(typeof(IAmLazyDBObject)))
335+
{
336+
throw new BTDBException("Lazy DB object serialization is forbidden. Type: "+type.ToSimpleName());
337+
}
332338
if (!type.IsSubclassOf(typeof(Delegate)))
333339
{
334340
if (type.IsGenericType)

BTDB/EventStoreLayer/TypeSerializers.cs

+5
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ public BuildFromTypeCtx(TypeSerializers typeSerializers,
131131
{
132132
if (_type2DescriptorMap.TryGetValue(type, out var result)) return result;
133133
if (_temporaryMap.TryGetValue(type, out result)) return result;
134+
if (_typeSerializersOptions.ForbidSerializeLazyDBObjects &&
135+
type.InheritsOrImplements(typeof(IAmLazyDBObject)))
136+
{
137+
throw new BTDBException("Lazy DB object serialization is forbidden. Type: " + type.ToSimpleName());
138+
}
134139
if (!type.IsSubclassOf(typeof(Delegate)))
135140
{
136141
if (type.IsGenericType)

BTDB/EventStoreLayer/TypeSerializersOptions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ public class TypeSerializersOptions
2020
public TypeDescriptorOptions? TypeDescriptorOptions { get; set; }
2121

2222
public ITypeConvertorGenerator? ConvertorGenerator { get; set; }
23+
24+
public bool ForbidSerializeLazyDBObjects { get; set; }
2325
}

BTDB/ODBLayer/IAmLazyDBObject.cs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace BTDB.ODBLayer;
2+
3+
// Just marker interface to mark object as lazy loaded, which could be problem if done in different thread or after transaction is closed
4+
public interface IAmLazyDBObject
5+
{
6+
}

BTDB/ODBLayer/ODBDictionary.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace BTDB.ODBLayer;
1818

1919
delegate void FreeContentFun(IInternalObjectDBTransaction transaction, ref MemReader reader, IList<ulong> dictIds);
2020

21-
public class ODBDictionary<TKey, TValue> : IOrderedDictionary<TKey, TValue>, IQuerySizeDictionary<TKey>
21+
public class ODBDictionary<TKey, TValue> : IOrderedDictionary<TKey, TValue>, IQuerySizeDictionary<TKey>, IAmLazyDBObject
2222
{
2323
readonly IInternalObjectDBTransaction _tr;
2424
readonly IFieldHandler _keyHandler;

BTDB/ODBLayer/ODBSet.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace BTDB.ODBLayer;
1212

13-
public class ODBSet<TKey> : IOrderedSet<TKey>, IQuerySizeDictionary<TKey>
13+
public class ODBSet<TKey> : IOrderedSet<TKey>, IQuerySizeDictionary<TKey>, IAmLazyDBObject
1414
{
1515
readonly IInternalObjectDBTransaction _tr;
1616
readonly IFieldHandler _keyHandler;
+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.Linq;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
using BTDB.Buffer;
8+
using BTDB.Collections;
9+
using BTDB.EventStore2Layer;
10+
using BTDB.EventStoreLayer;
11+
using BTDB.KVDBLayer;
12+
using BTDB.ODBLayer;
13+
using Xunit;
14+
using Xunit.Abstractions;
15+
16+
namespace BTDBTest;
17+
18+
public class ObjectDbEventSerializeTest : ObjectDbTestBase
19+
{
20+
public ObjectDbEventSerializeTest(ITestOutputHelper output) : base(output)
21+
{
22+
}
23+
24+
public class Item
25+
{
26+
public ulong Id { get; set; }
27+
public string Name { get; set; }
28+
}
29+
30+
public class ObjWithIDictionary
31+
{
32+
[PrimaryKey(1)] public uint TenantId { get; set; }
33+
34+
public IDictionary<ulong, Item> Dict { get; set; }
35+
}
36+
37+
public interface IObjWithIDictionaryTable : IRelation<ObjWithIDictionary>
38+
{
39+
}
40+
41+
[Fact]
42+
public void SerializeLazyDictionaryToEventLayerWorks()
43+
{
44+
using var tr = _db.StartTransaction();
45+
var t = tr.GetRelation<IObjWithIDictionaryTable>();
46+
var obj = new ObjWithIDictionary
47+
{ TenantId = 1, Dict = new Dictionary<ulong, Item> { { 1, new Item { Id = 1, Name = "A" } } } };
48+
t.Upsert(obj);
49+
var obj2 = t.First();
50+
Assert.IsType<ODBDictionary<ulong, Item>>(obj2.Dict);
51+
52+
var storage = new MemoryEventFileStorage();
53+
54+
var manager = new EventStoreManager();
55+
var appender = manager.AppendToStore(storage);
56+
appender.Store(null, [obj2]);
57+
appender.FinalizeStore();
58+
59+
manager = new EventStoreManager();
60+
var eventObserver = new EventStoreTest.StoringEventObserver();
61+
var reader = manager.OpenReadOnlyStore(storage);
62+
reader.ReadToEnd(eventObserver);
63+
64+
var obj3 = eventObserver.Events[0][0] as ObjWithIDictionary;
65+
Assert.NotNull(obj3);
66+
Assert.Equal(obj.TenantId, obj3.TenantId);
67+
Assert.Equal(obj.Dict.Count, obj3.Dict.Count);
68+
Assert.Equal(obj.Dict.First().Key, obj3.Dict.First().Key);
69+
Assert.Equal(obj.Dict.First().Value.Id, obj3.Dict.First().Value.Id);
70+
Assert.Equal(obj.Dict.First().Value.Name, obj3.Dict.First().Value.Name);
71+
}
72+
73+
[Fact]
74+
public void SerializeLazyDictionaryToEventLayer2Works()
75+
{
76+
using var tr = _db.StartTransaction();
77+
var t = tr.GetRelation<IObjWithIDictionaryTable>();
78+
var obj = new ObjWithIDictionary
79+
{ TenantId = 1, Dict = new Dictionary<ulong, Item> { { 1, new Item { Id = 1, Name = "A" } } } };
80+
t.Upsert(obj);
81+
var obj2 = t.First();
82+
Assert.IsType<ODBDictionary<ulong, Item>>(obj2.Dict);
83+
84+
var serializer = new EventSerializer();
85+
86+
var meta = serializer.Serialize(out var hasMetadata, obj2);
87+
serializer.ProcessMetadataLog(meta);
88+
var data = serializer.Serialize(out hasMetadata, obj2);
89+
90+
var deserializer = new EventDeserializer();
91+
Assert.False(deserializer.Deserialize(out var objx, data));
92+
deserializer.ProcessMetadataLog(meta);
93+
Assert.True(deserializer.Deserialize(out objx, data));
94+
95+
var obj3 = objx as ObjWithIDictionary;
96+
97+
Assert.NotNull(obj3);
98+
Assert.Equal(obj.TenantId, obj3.TenantId);
99+
Assert.Equal(obj.Dict.Count, obj3.Dict.Count);
100+
Assert.Equal(obj.Dict.First().Key, obj3.Dict.First().Key);
101+
Assert.Equal(obj.Dict.First().Value.Id, obj3.Dict.First().Value.Id);
102+
Assert.Equal(obj.Dict.First().Value.Name, obj3.Dict.First().Value.Name);
103+
}
104+
105+
[Fact]
106+
public void SerializeLazyDictionaryToEventLayerMustThrowIfForbidden()
107+
{
108+
using var tr = _db.StartTransaction();
109+
var t = tr.GetRelation<IObjWithIDictionaryTable>();
110+
var obj = new ObjWithIDictionary
111+
{ TenantId = 1, Dict = new Dictionary<ulong, Item> { { 1, new Item { Id = 1, Name = "A" } } } };
112+
t.Upsert(obj);
113+
var obj2 = t.First();
114+
Assert.IsType<ODBDictionary<ulong, Item>>(obj2.Dict);
115+
116+
var storage = new MemoryEventFileStorage();
117+
118+
var manager = new EventStoreManager(new() { ForbidSerializeLazyDBObjects = true });
119+
var appender = manager.AppendToStore(storage);
120+
Assert.Throws<BTDBException>(() => appender.Store(null, [obj2]));
121+
}
122+
123+
[Fact]
124+
public void SerializeLazyDictionaryToEventLayer2MustThrowIfForbidden()
125+
{
126+
using var tr = _db.StartTransaction();
127+
var t = tr.GetRelation<IObjWithIDictionaryTable>();
128+
var obj = new ObjWithIDictionary
129+
{ TenantId = 1, Dict = new Dictionary<ulong, Item> { { 1, new Item { Id = 1, Name = "A" } } } };
130+
t.Upsert(obj);
131+
var obj2 = t.First();
132+
Assert.IsType<ODBDictionary<ulong, Item>>(obj2.Dict);
133+
134+
var serializer = new EventSerializer(forbidSerializationOfLazyDBObjects: true);
135+
136+
Assert.Throws<BTDBException>(() => serializer.Serialize(out var hasMetadata, obj2));
137+
}
138+
}

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [unreleased]
44

5+
### Added
6+
7+
New option in EventLayer and EventLayer2 to forbid serialization of lazy DB objects like `IDictionary<,>`
8+
59
## 32.11.0
610

711
### Fixed

0 commit comments

Comments
 (0)