1010import gc
1111
1212
13+ OBJECT_REF_STORAGE_CLASS = 13
14+ UNIQUE_ADDRESS_INSTANCE_ID_SHIFT = 14
15+ BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
16+
17+
18+ def _base32_encode (data ):
19+ table = (
20+ (0b11111000 , 3 ), (0b00000111 , - 2 ), (0b11000000 , 6 ), (0b00111110 , 1 ),
21+ (0b00000001 , - 4 ), (0b11110000 , 4 ), (0b00001111 , - 1 ), (0b10000000 , 7 ),
22+ (0b01111100 , 2 ), (0b00000011 , - 3 ), (0b11100000 , 5 ), (0b00011111 , 0 ),
23+ )
24+ strides = (1 , 2 , 1 , 2 , 2 , 1 , 2 , 1 )
25+ result = []
26+ data_at = 0
27+ table_at = 0
28+ stride_at = 0
29+ while data_at < len (data ):
30+ enc_value = 0
31+ in_val = data [data_at ]
32+ for _ in range (strides [stride_at ]):
33+ mask , shift = table [table_at ]
34+ if shift > 0 :
35+ enc_value |= (in_val & mask ) >> shift
36+ else :
37+ enc_value |= (in_val & mask ) << - shift
38+ data_at += 1
39+ in_val = data [data_at ] if data_at != len (data ) else 0
40+ table_at = (table_at + 1 ) % len (table )
41+ result .append (BASE32_CHARS [enc_value ])
42+ stride_at = (stride_at + 1 ) % len (strides )
43+ return "" .join (result )
44+
45+
46+ def _base32_decode (data ):
47+ table = (
48+ (0b00011111 , 3 ), (0b00011100 , - 2 ), (0b00000011 , 6 ), (0b00011111 , 1 ),
49+ (0b00010000 , - 4 ), (0b00001111 , 4 ), (0b00011110 , - 1 ), (0b00000001 , 7 ),
50+ (0b00011111 , 2 ), (0b00011000 , - 3 ), (0b00000111 , 5 ), (0b00011111 , 0 ),
51+ )
52+ strides = (2 , 3 , 2 , 3 , 2 )
53+ result = bytearray ([0 ])
54+ table_at = 0
55+ stride_at = 0
56+ stride = strides [stride_at ]
57+ for char_at , char in enumerate (data ):
58+ value = BASE32_CHARS .index (char )
59+ while True :
60+ mask , shift = table [table_at ]
61+ if shift >= 0 :
62+ result [- 1 ] |= (value & mask ) << shift
63+ table_at = (table_at + 1 ) % len (table )
64+ stride -= 1
65+ if stride == 0 and char_at != len (data ) - 1 :
66+ result .append (0 )
67+ stride_at = (stride_at + 1 ) % len (strides )
68+ stride = strides [stride_at ]
69+ break
70+ result [- 1 ] |= (value & mask ) >> - shift
71+ table_at = (table_at + 1 ) % len (table )
72+ stride -= 1
73+ if stride == 0 :
74+ result .append (0 )
75+ stride_at += 1
76+ stride = strides [stride_at ]
77+ return bytes (result )
78+
79+
80+ def _write_packed_int (value ):
81+ result = bytearray ([value & 0x7F ])
82+ value >>= 7
83+ while value :
84+ result .insert (0 , (value & 0x7F ) | 0x80 )
85+ value >>= 7
86+ return bytes (result )
87+
88+
89+ def _read_packed_int (data , at ):
90+ value = 0
91+ while data [at ] & 0x80 :
92+ value |= data [at ] & 0x7F
93+ value <<= 7
94+ at += 1
95+ value |= data [at ] & 0x7F
96+ return value , at + 1
97+
98+
99+ def _decode_uuid (uuid ):
100+ data = _base32_decode (uuid )
101+ fixture_uuid = int .from_bytes (data [:8 ], "little" )
102+ unique_address , at = _read_packed_int (data , 8 )
103+ storage_class , _ = _read_packed_int (data , at )
104+ return fixture_uuid , unique_address , storage_class
105+
106+
107+ def _encode_uuid (fixture_uuid , unique_address , storage_class ):
108+ data = (
109+ fixture_uuid .to_bytes (8 , "little" )
110+ + _write_packed_int (unique_address )
111+ + _write_packed_int (storage_class )
112+ )
113+ return _base32_encode (data )
114+
115+
13116@db0 .memo (immutable = True , no_default_tags = True )
14117@dataclass
15118class MemoImmutableClass1 :
@@ -36,6 +139,13 @@ class MemoImmutableNestedPayload:
36139 count : int
37140
38141
142+ @db0 .memo (no_default_tags = True )
143+ class MemoRegularFetchUUIDPayload :
144+ def __init__ (self , name , count ):
145+ self .name = name
146+ self .count = count
147+
148+
39149@db0 .memo (immutable = True , no_default_tags = True )
40150class MemoImmutableNestedHolder :
41151 def __init__ (self , name , count , label ):
@@ -50,6 +160,27 @@ def __init__(self, nested, label):
50160 self .label = label
51161
52162
163+ @db0 .memo (immutable = True , no_default_tags = True )
164+ class MemoImmutableDeepLeaf :
165+ def __init__ (self , name , count ):
166+ self .name = name
167+ self .count = count
168+
169+
170+ @db0 .memo (immutable = True , no_default_tags = True )
171+ class MemoImmutableDeepMiddle :
172+ def __init__ (self , name , count ):
173+ self .name = name
174+ self .leaf = MemoImmutableDeepLeaf (name = f"{ name } -leaf" , count = count )
175+
176+
177+ @db0 .memo (immutable = True , no_default_tags = True )
178+ class MemoImmutableDeepRoot :
179+ def __init__ (self , name , count ):
180+ self .middle = MemoImmutableDeepMiddle (name = name , count = count )
181+ self .label = "deep-root"
182+
183+
53184@db0 .memo (immutable = True , no_default_tags = True )
54185class MemoImmutableTupleHolder :
55186 def __init__ (self , payload ):
@@ -86,6 +217,120 @@ def test_create_memo_immutable(db0_fixture):
86217 _ = MemoImmutableClass1 (data = "immutable data" , value = 42 )
87218
88219
220+ def test_uuid_and_fetch_regular_memo_object (db0_fixture ):
221+ obj = MemoRegularFetchUUIDPayload ("regular uuid" , 101 )
222+ obj_uuid = db0 .uuid (obj )
223+
224+ assert db0 .fetch (obj_uuid ) is obj
225+ assert db0 .fetch (MemoRegularFetchUUIDPayload , obj_uuid ) is obj
226+
227+ db0 .tags (obj ).add ("keep-regular-fetch-uuid" )
228+ db0 .commit ()
229+ db0 .close ()
230+ db0 .init (DB0_DIR )
231+ db0 .open ("my-test-prefix" , "rw" )
232+
233+ reopened = db0 .fetch (obj_uuid )
234+ assert isinstance (reopened , MemoRegularFetchUUIDPayload )
235+ assert reopened .name == "regular uuid"
236+ assert reopened .count == 101
237+
238+
239+ def test_uuid_and_fetch_immutable_root_object (db0_fixture ):
240+ obj = MemoImmutableClass1 (data = "immutable uuid" , value = 102 )
241+ db0 .tags (obj ).add ("keep-immutable-fetch-uuid" )
242+ obj_uuid = db0 .uuid (obj )
243+
244+ assert db0 .fetch (obj_uuid ) is obj
245+ assert db0 .fetch (MemoImmutableClass1 , obj_uuid ) is obj
246+
247+ db0 .commit ()
248+ db0 .close ()
249+ db0 .init (DB0_DIR )
250+ db0 .open ("my-test-prefix" , "rw" )
251+
252+ reopened = db0 .fetch (obj_uuid )
253+ assert isinstance (reopened , MemoImmutableClass1 )
254+ assert reopened .data == "immutable uuid"
255+ assert reopened .value == 102
256+
257+
258+ def test_uuid_and_fetch_embedded_nested_immutable_object (db0_fixture ):
259+ root = MemoImmutableNestedHolder (name = "embedded uuid" , count = 103 , label = "root" )
260+ db0 .tags (root ).add ("keep-embedded-fetch-uuid" )
261+ nested = root .nested
262+ nested_uuid = db0 .uuid (nested )
263+
264+ assert nested_uuid != db0 .uuid (root )
265+ fetched = db0 .fetch (nested_uuid )
266+ fetched_by_type = db0 .fetch (MemoImmutableNestedPayload , nested_uuid )
267+ assert isinstance (fetched , MemoImmutableNestedPayload )
268+ assert fetched .name == "embedded uuid"
269+ assert fetched .count == 103
270+ assert db0 .uuid (fetched ) == nested_uuid
271+ assert db0 .uuid (fetched_by_type ) == nested_uuid
272+
273+ db0 .commit ()
274+ db0 .close ()
275+ db0 .init (DB0_DIR )
276+ db0 .open ("my-test-prefix" , "rw" )
277+
278+ reopened = db0 .fetch (nested_uuid )
279+ assert isinstance (reopened , MemoImmutableNestedPayload )
280+ assert reopened .name == "embedded uuid"
281+ assert reopened .count == 103
282+
283+
284+ def test_uuid_and_fetch_deeply_embedded_immutable_objects (db0_fixture ):
285+ root = MemoImmutableDeepRoot (name = "deep embedded uuid" , count = 104 )
286+ db0 .tags (root ).add ("keep-deep-embedded-fetch-uuid" )
287+ middle = root .middle
288+ leaf = middle .leaf
289+ middle_uuid = db0 .uuid (middle )
290+ leaf_uuid = db0 .uuid (leaf )
291+
292+ assert middle_uuid != db0 .uuid (root )
293+ assert leaf_uuid != middle_uuid
294+ fetched_middle = db0 .fetch (middle_uuid )
295+ fetched_leaf = db0 .fetch (leaf_uuid )
296+ assert isinstance (fetched_middle , MemoImmutableDeepMiddle )
297+ assert isinstance (fetched_leaf , MemoImmutableDeepLeaf )
298+ assert fetched_middle .name == "deep embedded uuid"
299+ assert fetched_leaf .name == "deep embedded uuid-leaf"
300+ assert db0 .uuid (fetched_middle ) == middle_uuid
301+ assert db0 .uuid (fetched_leaf ) == leaf_uuid
302+
303+ db0 .commit ()
304+ db0 .close ()
305+ db0 .init (DB0_DIR )
306+ db0 .open ("my-test-prefix" , "rw" )
307+
308+ reopened_middle = db0 .fetch (middle_uuid )
309+ reopened_leaf = db0 .fetch (leaf_uuid )
310+ assert isinstance (reopened_middle , MemoImmutableDeepMiddle )
311+ assert isinstance (reopened_leaf , MemoImmutableDeepLeaf )
312+ assert reopened_middle .name == "deep embedded uuid"
313+ assert reopened_middle .leaf .name == "deep embedded uuid-leaf"
314+ assert reopened_leaf .name == "deep embedded uuid-leaf"
315+ assert reopened_leaf .count == 104
316+
317+
318+ def test_fetch_rejects_invalid_embedded_uuid_inside_existing_allocation (db0_fixture ):
319+ root = MemoImmutableDeepRoot (name = "bad embedded uuid" , count = 105 )
320+ db0 .tags (root ).add ("keep-invalid-embedded-fetch-uuid" )
321+ leaf_uuid = db0 .uuid (root .middle .leaf )
322+ fixture_uuid , unique_address , storage_class = _decode_uuid (leaf_uuid )
323+ assert storage_class == OBJECT_REF_STORAGE_CLASS
324+
325+ address = unique_address >> UNIQUE_ADDRESS_INSTANCE_ID_SHIFT
326+ instance_id = unique_address & ((1 << UNIQUE_ADDRESS_INSTANCE_ID_SHIFT ) - 1 )
327+ invalid_unique_address = ((address + 1 ) << UNIQUE_ADDRESS_INSTANCE_ID_SHIFT ) | instance_id
328+ invalid_uuid = _encode_uuid (fixture_uuid , invalid_unique_address , storage_class )
329+
330+ with pytest .raises (Exception ):
331+ db0 .fetch (invalid_uuid )
332+
333+
89334def test_tag_and_find_immutable_instance (db0_fixture ):
90335 obj_1 = MemoImmutableClass1 (data = "immutable data" , value = 42 )
91336 db0 .tags (obj_1 ).add ("tag1" , "tag2" )
@@ -176,23 +421,28 @@ def test_prebound_immutable_nested_object_embeds_into_owner(db0_fixture):
176421 assert db0 .uuid (inner ) != db0 .uuid (obj )
177422
178423
179- @pytest .mark .skip (
180- reason = "TODO: embedded immutable references can be created, but unreferencing by inner address is not implemented yet."
181- )
182424def test_regular_memo_can_reference_embedded_immutable_nested_object (db0_fixture ):
183425 outer = MemoImmutableNestedHolder (name = "referenced child" , count = 21 , label = "root" )
184426 db0 .tags (outer ).add ("keep-reference-source" )
185427 inner = outer .nested
186428
187429 holder = MemoSetReferenceHolder (inner )
430+ db0 .tags (holder ).add ("keep-regular-embedded-reference" )
431+ holder_id = db0 .uuid (holder )
188432
189433 assert db0 .uuid (inner ) != db0 .uuid (outer )
190434 assert db0 .uuid (holder )
191435
436+ db0 .commit ()
437+ db0 .close ()
438+ db0 .init (DB0_DIR )
439+ db0 .open ("my-test-prefix" , "rw" )
440+
441+ reopened = db0 .fetch (holder_id )
442+ assert reopened .payload .name == "referenced child"
443+ assert reopened .payload .count == 21
444+
192445
193- @pytest .mark .skip (
194- reason = "TODO: embedded immutable references can be created, but unreferencing by inner address is not implemented yet."
195- )
196446def test_db0_collections_can_store_embedded_immutable_nested_object_reference (db0_fixture ):
197447 outer = MemoImmutableNestedHolder (name = "collection child" , count = 22 , label = "root" )
198448 db0 .tags (outer ).add ("keep-collection-source" )
@@ -201,16 +451,30 @@ def test_db0_collections_can_store_embedded_immutable_nested_object_reference(db
201451 list_holder = MemoSetReferenceHolder (db0 .list ([inner ]))
202452 set_holder = MemoSetReferenceHolder (db0 .set ([inner ]))
203453 dict_holder = MemoSetReferenceHolder (db0 .dict ({"child" : inner , inner : "value" }))
454+ db0 .tags (list_holder ).add ("keep-list-embedded-reference" )
455+ db0 .tags (set_holder ).add ("keep-set-embedded-reference" )
456+ db0 .tags (dict_holder ).add ("keep-dict-embedded-reference" )
457+ list_holder_id = db0 .uuid (list_holder )
458+ set_holder_id = db0 .uuid (set_holder )
459+ dict_holder_id = db0 .uuid (dict_holder )
204460
205461 assert db0 .uuid (inner ) != db0 .uuid (outer )
206462 assert db0 .uuid (list_holder )
207463 assert db0 .uuid (set_holder )
208464 assert db0 .uuid (dict_holder )
209465
466+ db0 .commit ()
467+ db0 .close ()
468+ db0 .init (DB0_DIR )
469+ db0 .open ("my-test-prefix" , "rw" )
470+
471+ assert db0 .fetch (list_holder_id ).payload [0 ].name == "collection child"
472+ assert next (iter (db0 .fetch (set_holder_id ).payload )).count == 22
473+ reopened_dict = db0 .fetch (dict_holder_id ).payload
474+ assert reopened_dict ["child" ].name == "collection child"
475+ assert reopened_dict [reopened_dict ["child" ]] == "value"
476+
210477
211- @pytest .mark .skip (
212- reason = "TODO: embedded immutable references can be created, but unreferencing by inner address is not implemented yet."
213- )
214478def test_index_can_store_embedded_immutable_nested_object_reference (db0_fixture ):
215479 outer = MemoImmutableNestedHolder (name = "index child" , count = 23 , label = "root" )
216480 db0 .tags (outer ).add ("keep-index-source" )
@@ -219,10 +483,24 @@ def test_index_can_store_embedded_immutable_nested_object_reference(db0_fixture)
219483
220484 index .add (1 , inner )
221485 index .flush ()
486+ holder = MemoSetReferenceHolder (index )
487+ db0 .tags (holder ).add ("keep-index-embedded-reference" )
488+ holder_id = db0 .uuid (holder )
222489
223490 assert db0 .uuid (inner ) != db0 .uuid (outer )
224491 assert len (index ) == 1
225492
493+ db0 .commit ()
494+ db0 .close ()
495+ db0 .init (DB0_DIR )
496+ db0 .open ("my-test-prefix" , "rw" )
497+
498+ reopened_index = db0 .fetch (holder_id ).payload
499+ retrieved = list (reopened_index .select ())
500+ assert len (retrieved ) == 1
501+ assert retrieved [0 ].name == "index child"
502+ assert retrieved [0 ].count == 23
503+
226504
227505def test_read_embedded_tuple_field (db0_fixture ):
228506 payload = tuple (f"alpha-{ index } " for index in range (12 )) + (7 , b"bytes" , None )
0 commit comments