Skip to content

Commit f3a1754

Browse files
author
Wojtek
committed
referencing immutable and embedded objects
1 parent 890414d commit f3a1754

25 files changed

Lines changed: 594 additions & 147 deletions

‎python_tests/test_memo_immutable.py‎

Lines changed: 287 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,109 @@
1010
import 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
15118
class 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)
40150
class 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)
54185
class 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+
89334
def 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-
)
182424
def 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-
)
196446
def 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-
)
214478
def 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

227505
def 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

Comments
 (0)