Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Types derived from `v_object` should follow the project-wide constructor pattern
### C++ style

- Use camelCase for local helper variables, lambdas, and method names in C++ code.
- Project types often avoid implicit bool conversion because it can hide subtle ownership, state, and null-check bugs. Use explicit double-negation checks such as `if (!!obj)` or `while (!!item)` when a type supports `operator!()`.

### Python binding wrapper access

Expand Down
2 changes: 1 addition & 1 deletion dbzero/dbzero/dbzero.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def load_dynamic(name, path):

def __bootstrap__():
global __bootstrap__, __loader__, __file__
paths = [os.path.join(os.path.split(__file__)[0]), "/src/dev/build/release", "/usr/local/lib/python3/dist-packages/dbzero/"]
paths = [os.path.join(os.path.split(__file__)[0]), "/src/dev/build/debug", "/usr/local/lib/python3/dist-packages/dbzero/"]
__file__ = None
for path in paths:
if os.path.isdir(path):
Expand Down
128 changes: 127 additions & 1 deletion python_tests/test_memo_immutable.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,56 @@
from dataclasses import dataclass
from .conftest import DB0_DIR
import random
import gc


@db0.memo(immutable=True, no_default_tags=True)
@dataclass
class MemoImmutableClass1:
data: str
value: int = 0


@db0.memo(immutable=True, no_default_tags=True)
@dataclass
class MemoImmutableBytesClass:
data: bytes


@db0.memo(immutable=True, no_default_tags=True)
@dataclass
class MemoImmutableLargePayloadClass:
data: object


@db0.memo(immutable=True, no_default_tags=True)
@dataclass
class MemoImmutableNestedPayload:
name: str
count: int


@db0.memo(immutable=True, no_default_tags=True)
class MemoImmutableNestedHolder:
def __init__(self, name, count, label):
self.nested = MemoImmutableNestedPayload(name=name, count=count)
self.label = label


@db0.memo(immutable=True, no_default_tags=True)
class MemoImmutablePreboundNestedHolder:
def __init__(self, nested, label):
self.nested = nested
self.label = label


@db0.memo(immutable=True, no_default_tags=True)
class MemoImmutableReadInConstructor:
def __init__(self, data, payload):
self.data = data
self.payload = payload
self.seen_data = self.data
self.seen_payload = self.payload

def test_create_memo_immutable(db0_fixture):
_ = MemoImmutableClass1(data="immutable data", value=42)
Expand All @@ -23,4 +66,87 @@ def test_tag_and_find_immutable_instance(db0_fixture):
obj_1 = MemoImmutableClass1(data="immutable data", value=42)
db0.tags(obj_1).add("tag1", "tag2")
assert list(db0.find("tag1")) == [obj_1]



def test_read_embedded_immutable_string_after_reopen(db0_fixture):
obj = MemoImmutableClass1(data="small embedded string", value=7)
db0.tags(obj).add("keep-embedded-string")
obj_id = db0.uuid(obj)
assert obj.data == "small embedded string"
assert db0.fetch(obj_id).data == "small embedded string"

del obj
gc.collect()
db0.commit()
db0.close()
db0.init(DB0_DIR)
db0.open("my-test-prefix", "rw")

reopened = db0.fetch(obj_id)
assert reopened.data == "small embedded string"
assert reopened.value == 7
del reopened
gc.collect()


def test_read_embedded_immutable_bytes(db0_fixture):
payload = b"a\x00b embedded bytes"
obj = MemoImmutableBytesClass(payload)
assert obj.data == payload


def test_large_immutable_string_and_bytes_fallback_read(db0_fixture):
large_text = "x" * (12 * 1024)
large_bytes = b"y" * (12 * 1024)

text_obj = MemoImmutableLargePayloadClass(large_text)
bytes_obj = MemoImmutableLargePayloadClass(large_bytes)

assert text_obj.data == large_text
assert bytes_obj.data == large_bytes


def test_read_embedded_immutable_values_inside_constructor(db0_fixture):
payload = b"constructor\x00bytes"
obj = MemoImmutableReadInConstructor("constructor string", payload)

assert obj.seen_data == "constructor string"
assert obj.seen_payload == payload
assert obj.data == "constructor string"
assert obj.payload == payload


def test_read_embedded_immutable_nested_object_after_reopen(db0_fixture):
obj = MemoImmutableNestedHolder(name="embedded child", count=5, label="root")
db0.tags(obj).add("keep-embedded-nested")
obj_id = db0.uuid(obj)

assert obj.nested.name == "embedded child"
assert obj.nested.count == 5

del obj
gc.collect()
db0.commit()
db0.close()
db0.init(DB0_DIR)
db0.open("my-test-prefix", "rw")

reopened = db0.fetch(obj_id)
assert reopened.nested.name == "embedded child"
assert reopened.nested.count == 5
assert isinstance(reopened.nested, MemoImmutableNestedPayload)


def test_prebound_immutable_nested_object_embeds_into_owner(db0_fixture):
inner = MemoImmutableNestedPayload(name="prebound child", count=8)
obj = MemoImmutablePreboundNestedHolder(inner, "root")
db0.tags(obj).add("keep-prebound-embedded")

assert obj.nested.name == "prebound child"
assert inner.name == "prebound child"
assert inner.count == 8
assert isinstance(inner, MemoImmutableNestedPayload)
assert db0.is_memo(inner)
with pytest.raises(Exception):
db0.uuid(inner)

Loading
Loading