diff --git a/python_tests/test_memo_immutable.py b/python_tests/test_memo_immutable.py index 264e7839..9d460b13 100644 --- a/python_tests/test_memo_immutable.py +++ b/python_tests/test_memo_immutable.py @@ -10,6 +10,109 @@ import gc +OBJECT_REF_STORAGE_CLASS = 13 +UNIQUE_ADDRESS_INSTANCE_ID_SHIFT = 14 +BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" + + +def _base32_encode(data): + table = ( + (0b11111000, 3), (0b00000111, -2), (0b11000000, 6), (0b00111110, 1), + (0b00000001, -4), (0b11110000, 4), (0b00001111, -1), (0b10000000, 7), + (0b01111100, 2), (0b00000011, -3), (0b11100000, 5), (0b00011111, 0), + ) + strides = (1, 2, 1, 2, 2, 1, 2, 1) + result = [] + data_at = 0 + table_at = 0 + stride_at = 0 + while data_at < len(data): + enc_value = 0 + in_val = data[data_at] + for _ in range(strides[stride_at]): + mask, shift = table[table_at] + if shift > 0: + enc_value |= (in_val & mask) >> shift + else: + enc_value |= (in_val & mask) << -shift + data_at += 1 + in_val = data[data_at] if data_at != len(data) else 0 + table_at = (table_at + 1) % len(table) + result.append(BASE32_CHARS[enc_value]) + stride_at = (stride_at + 1) % len(strides) + return "".join(result) + + +def _base32_decode(data): + table = ( + (0b00011111, 3), (0b00011100, -2), (0b00000011, 6), (0b00011111, 1), + (0b00010000, -4), (0b00001111, 4), (0b00011110, -1), (0b00000001, 7), + (0b00011111, 2), (0b00011000, -3), (0b00000111, 5), (0b00011111, 0), + ) + strides = (2, 3, 2, 3, 2) + result = bytearray([0]) + table_at = 0 + stride_at = 0 + stride = strides[stride_at] + for char_at, char in enumerate(data): + value = BASE32_CHARS.index(char) + while True: + mask, shift = table[table_at] + if shift >= 0: + result[-1] |= (value & mask) << shift + table_at = (table_at + 1) % len(table) + stride -= 1 + if stride == 0 and char_at != len(data) - 1: + result.append(0) + stride_at = (stride_at + 1) % len(strides) + stride = strides[stride_at] + break + result[-1] |= (value & mask) >> -shift + table_at = (table_at + 1) % len(table) + stride -= 1 + if stride == 0: + result.append(0) + stride_at += 1 + stride = strides[stride_at] + return bytes(result) + + +def _write_packed_int(value): + result = bytearray([value & 0x7F]) + value >>= 7 + while value: + result.insert(0, (value & 0x7F) | 0x80) + value >>= 7 + return bytes(result) + + +def _read_packed_int(data, at): + value = 0 + while data[at] & 0x80: + value |= data[at] & 0x7F + value <<= 7 + at += 1 + value |= data[at] & 0x7F + return value, at + 1 + + +def _decode_uuid(uuid): + data = _base32_decode(uuid) + fixture_uuid = int.from_bytes(data[:8], "little") + unique_address, at = _read_packed_int(data, 8) + storage_class, _ = _read_packed_int(data, at) + return fixture_uuid, unique_address, storage_class + + +def _encode_uuid(fixture_uuid, unique_address, storage_class): + data = ( + fixture_uuid.to_bytes(8, "little") + + _write_packed_int(unique_address) + + _write_packed_int(storage_class) + ) + return _base32_encode(data) + + @db0.memo(immutable=True, no_default_tags=True) @dataclass class MemoImmutableClass1: @@ -36,6 +139,13 @@ class MemoImmutableNestedPayload: count: int +@db0.memo(no_default_tags=True) +class MemoRegularFetchUUIDPayload: + def __init__(self, name, count): + self.name = name + self.count = count + + @db0.memo(immutable=True, no_default_tags=True) class MemoImmutableNestedHolder: def __init__(self, name, count, label): @@ -50,6 +160,27 @@ def __init__(self, nested, label): self.label = label +@db0.memo(immutable=True, no_default_tags=True) +class MemoImmutableDeepLeaf: + def __init__(self, name, count): + self.name = name + self.count = count + + +@db0.memo(immutable=True, no_default_tags=True) +class MemoImmutableDeepMiddle: + def __init__(self, name, count): + self.name = name + self.leaf = MemoImmutableDeepLeaf(name=f"{name}-leaf", count=count) + + +@db0.memo(immutable=True, no_default_tags=True) +class MemoImmutableDeepRoot: + def __init__(self, name, count): + self.middle = MemoImmutableDeepMiddle(name=name, count=count) + self.label = "deep-root" + + @db0.memo(immutable=True, no_default_tags=True) class MemoImmutableTupleHolder: def __init__(self, payload): @@ -86,6 +217,120 @@ def test_create_memo_immutable(db0_fixture): _ = MemoImmutableClass1(data="immutable data", value=42) +def test_uuid_and_fetch_regular_memo_object(db0_fixture): + obj = MemoRegularFetchUUIDPayload("regular uuid", 101) + obj_uuid = db0.uuid(obj) + + assert db0.fetch(obj_uuid) is obj + assert db0.fetch(MemoRegularFetchUUIDPayload, obj_uuid) is obj + + db0.tags(obj).add("keep-regular-fetch-uuid") + db0.commit() + db0.close() + db0.init(DB0_DIR) + db0.open("my-test-prefix", "rw") + + reopened = db0.fetch(obj_uuid) + assert isinstance(reopened, MemoRegularFetchUUIDPayload) + assert reopened.name == "regular uuid" + assert reopened.count == 101 + + +def test_uuid_and_fetch_immutable_root_object(db0_fixture): + obj = MemoImmutableClass1(data="immutable uuid", value=102) + db0.tags(obj).add("keep-immutable-fetch-uuid") + obj_uuid = db0.uuid(obj) + + assert db0.fetch(obj_uuid) is obj + assert db0.fetch(MemoImmutableClass1, obj_uuid) is obj + + db0.commit() + db0.close() + db0.init(DB0_DIR) + db0.open("my-test-prefix", "rw") + + reopened = db0.fetch(obj_uuid) + assert isinstance(reopened, MemoImmutableClass1) + assert reopened.data == "immutable uuid" + assert reopened.value == 102 + + +def test_uuid_and_fetch_embedded_nested_immutable_object(db0_fixture): + root = MemoImmutableNestedHolder(name="embedded uuid", count=103, label="root") + db0.tags(root).add("keep-embedded-fetch-uuid") + nested = root.nested + nested_uuid = db0.uuid(nested) + + assert nested_uuid != db0.uuid(root) + fetched = db0.fetch(nested_uuid) + fetched_by_type = db0.fetch(MemoImmutableNestedPayload, nested_uuid) + assert isinstance(fetched, MemoImmutableNestedPayload) + assert fetched.name == "embedded uuid" + assert fetched.count == 103 + assert db0.uuid(fetched) == nested_uuid + assert db0.uuid(fetched_by_type) == nested_uuid + + db0.commit() + db0.close() + db0.init(DB0_DIR) + db0.open("my-test-prefix", "rw") + + reopened = db0.fetch(nested_uuid) + assert isinstance(reopened, MemoImmutableNestedPayload) + assert reopened.name == "embedded uuid" + assert reopened.count == 103 + + +def test_uuid_and_fetch_deeply_embedded_immutable_objects(db0_fixture): + root = MemoImmutableDeepRoot(name="deep embedded uuid", count=104) + db0.tags(root).add("keep-deep-embedded-fetch-uuid") + middle = root.middle + leaf = middle.leaf + middle_uuid = db0.uuid(middle) + leaf_uuid = db0.uuid(leaf) + + assert middle_uuid != db0.uuid(root) + assert leaf_uuid != middle_uuid + fetched_middle = db0.fetch(middle_uuid) + fetched_leaf = db0.fetch(leaf_uuid) + assert isinstance(fetched_middle, MemoImmutableDeepMiddle) + assert isinstance(fetched_leaf, MemoImmutableDeepLeaf) + assert fetched_middle.name == "deep embedded uuid" + assert fetched_leaf.name == "deep embedded uuid-leaf" + assert db0.uuid(fetched_middle) == middle_uuid + assert db0.uuid(fetched_leaf) == leaf_uuid + + db0.commit() + db0.close() + db0.init(DB0_DIR) + db0.open("my-test-prefix", "rw") + + reopened_middle = db0.fetch(middle_uuid) + reopened_leaf = db0.fetch(leaf_uuid) + assert isinstance(reopened_middle, MemoImmutableDeepMiddle) + assert isinstance(reopened_leaf, MemoImmutableDeepLeaf) + assert reopened_middle.name == "deep embedded uuid" + assert reopened_middle.leaf.name == "deep embedded uuid-leaf" + assert reopened_leaf.name == "deep embedded uuid-leaf" + assert reopened_leaf.count == 104 + + +def test_fetch_rejects_invalid_embedded_uuid_inside_existing_allocation(db0_fixture): + root = MemoImmutableDeepRoot(name="bad embedded uuid", count=105) + db0.tags(root).add("keep-invalid-embedded-fetch-uuid") + leaf_uuid = db0.uuid(root.middle.leaf) + fixture_uuid, unique_address, storage_class = _decode_uuid(leaf_uuid) + assert storage_class == OBJECT_REF_STORAGE_CLASS + + address = unique_address >> UNIQUE_ADDRESS_INSTANCE_ID_SHIFT + instance_id = unique_address & ((1 << UNIQUE_ADDRESS_INSTANCE_ID_SHIFT) - 1) + invalid_unique_address = ((address + 1) << UNIQUE_ADDRESS_INSTANCE_ID_SHIFT) | instance_id + invalid_uuid = _encode_uuid(fixture_uuid, invalid_unique_address, storage_class) + + with pytest.raises(Exception): + db0.fetch(invalid_uuid) + + def test_tag_and_find_immutable_instance(db0_fixture): obj_1 = MemoImmutableClass1(data="immutable data", value=42) db0.tags(obj_1).add("tag1", "tag2") @@ -173,8 +418,88 @@ def test_prebound_immutable_nested_object_embeds_into_owner(db0_fixture): assert inner.count == 8 assert isinstance(inner, MemoImmutableNestedPayload) assert db0.is_memo(inner) - with pytest.raises(Exception): - db0.uuid(inner) + assert db0.uuid(inner) != db0.uuid(obj) + + +def test_regular_memo_can_reference_embedded_immutable_nested_object(db0_fixture): + outer = MemoImmutableNestedHolder(name="referenced child", count=21, label="root") + db0.tags(outer).add("keep-reference-source") + inner = outer.nested + + holder = MemoSetReferenceHolder(inner) + db0.tags(holder).add("keep-regular-embedded-reference") + holder_id = db0.uuid(holder) + + assert db0.uuid(inner) != db0.uuid(outer) + assert db0.uuid(holder) + + db0.commit() + db0.close() + db0.init(DB0_DIR) + db0.open("my-test-prefix", "rw") + + reopened = db0.fetch(holder_id) + assert reopened.payload.name == "referenced child" + assert reopened.payload.count == 21 + + +def test_db0_collections_can_store_embedded_immutable_nested_object_reference(db0_fixture): + outer = MemoImmutableNestedHolder(name="collection child", count=22, label="root") + db0.tags(outer).add("keep-collection-source") + inner = outer.nested + + list_holder = MemoSetReferenceHolder(db0.list([inner])) + set_holder = MemoSetReferenceHolder(db0.set([inner])) + dict_holder = MemoSetReferenceHolder(db0.dict({"child": inner, inner: "value"})) + db0.tags(list_holder).add("keep-list-embedded-reference") + db0.tags(set_holder).add("keep-set-embedded-reference") + db0.tags(dict_holder).add("keep-dict-embedded-reference") + list_holder_id = db0.uuid(list_holder) + set_holder_id = db0.uuid(set_holder) + dict_holder_id = db0.uuid(dict_holder) + + assert db0.uuid(inner) != db0.uuid(outer) + assert db0.uuid(list_holder) + assert db0.uuid(set_holder) + assert db0.uuid(dict_holder) + + db0.commit() + db0.close() + db0.init(DB0_DIR) + db0.open("my-test-prefix", "rw") + + assert db0.fetch(list_holder_id).payload[0].name == "collection child" + assert next(iter(db0.fetch(set_holder_id).payload)).count == 22 + reopened_dict = db0.fetch(dict_holder_id).payload + assert reopened_dict["child"].name == "collection child" + assert reopened_dict[reopened_dict["child"]] == "value" + + +def test_index_can_store_embedded_immutable_nested_object_reference(db0_fixture): + outer = MemoImmutableNestedHolder(name="index child", count=23, label="root") + db0.tags(outer).add("keep-index-source") + inner = outer.nested + index = db0.index() + + index.add(1, inner) + index.flush() + holder = MemoSetReferenceHolder(index) + db0.tags(holder).add("keep-index-embedded-reference") + holder_id = db0.uuid(holder) + + assert db0.uuid(inner) != db0.uuid(outer) + assert len(index) == 1 + + db0.commit() + db0.close() + db0.init(DB0_DIR) + db0.open("my-test-prefix", "rw") + + reopened_index = db0.fetch(holder_id).payload + retrieved = list(reopened_index.select()) + assert len(retrieved) == 1 + assert retrieved[0].name == "index child" + assert retrieved[0].count == 23 def test_read_embedded_tuple_field(db0_fixture): @@ -216,8 +541,7 @@ def test_embedded_tuple_with_prebound_immutable_object_element(db0_fixture): assert inner.count == 11 assert isinstance(inner, MemoImmutableNestedPayload) assert db0.is_memo(inner) - with pytest.raises(Exception): - db0.uuid(inner) + assert db0.uuid(inner) != db0.uuid(obj) def test_read_embedded_set_field_after_reopen(db0_fixture): @@ -258,8 +582,7 @@ def test_embedded_set_with_prebound_immutable_object_element(db0_fixture): assert inner.count == 13 assert isinstance(inner, MemoImmutableNestedPayload) assert db0.is_memo(inner) - with pytest.raises(Exception): - db0.uuid(inner) + assert db0.uuid(inner) != db0.uuid(obj) def test_python_set_lookup_survives_prebound_immutable_object_embedding(db0_fixture): @@ -271,8 +594,7 @@ def test_python_set_lookup_survives_prebound_immutable_object_embedding(db0_fixt assert inner in values assert "marker" in values assert inner.name == "python set embedded child" - with pytest.raises(Exception): - db0.uuid(inner) + assert db0.uuid(inner) != db0.uuid(obj) def test_python_set_accepts_transient_immutable_object(db0_fixture): @@ -385,8 +707,7 @@ def test_embedded_dict_with_prebound_immutable_object_value(db0_fixture): assert inner.count == 37 assert isinstance(inner, MemoImmutableNestedPayload) assert db0.is_memo(inner) - with pytest.raises(Exception): - db0.uuid(inner) + assert db0.uuid(inner) != db0.uuid(obj) def test_embedded_dict_with_prebound_immutable_object_key(db0_fixture): @@ -400,8 +721,7 @@ def test_embedded_dict_with_prebound_immutable_object_key(db0_fixture): assert inner in obj.payload assert inner.name == "dict key child" assert inner.count == 41 - with pytest.raises(Exception): - db0.uuid(inner) + assert db0.uuid(inner) != db0.uuid(obj) def test_embedded_dict_recursively_exposes_nested_collections(db0_fixture): diff --git a/src/dbzero/bindings/python/PyHash.cpp b/src/dbzero/bindings/python/PyHash.cpp index 27e155c5..8442936b 100644 --- a/src/dbzero/bindings/python/PyHash.cpp +++ b/src/dbzero/bindings/python/PyHash.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,9 @@ namespace db0::python template <> std::int64_t getPyHashImpl(db0::swine_ptr &, PyObject *key) { + if (PyEmbeddedMemo_Check(key)) { + return getEmbeddedMemoUniqueAddress(key).getValue(); + } auto &obj = reinterpret_cast(key)->ext(); if (!obj.hasInstance()) { THROWF(db0::InputException) << "Memo immutable object is not initialized" << THROWF_END; diff --git a/src/dbzero/bindings/python/PyInternalAPI.cpp b/src/dbzero/bindings/python/PyInternalAPI.cpp index c4104c26..6eefe0ec 100644 --- a/src/dbzero/bindings/python/PyInternalAPI.cpp +++ b/src/dbzero/bindings/python/PyInternalAPI.cpp @@ -4,6 +4,7 @@ #include "PyInternalAPI.hpp" #include "PyToolkit.hpp" #include "Memo.hpp" +#include #include #include #include @@ -141,21 +142,22 @@ namespace db0::python auto addr = object_id.m_address; if (storage_class == db0::object_model::StorageClass::OBJECT_REF) { auto &class_factory = db0::object_model::getClassFactory(*fixture); - // FIXME: this may be sped up if unloading object is avoided - auto result = PyToolkit::tryUnloadObject(fixture, addr, class_factory, nullptr, addr.getInstanceId()); + // Keep exists() non-throwing for deleted root objects. Embedded-address fetch + // resolution is handled by fetchObject(); existence checks for embedded UUIDs + // are not part of this feature slice. + auto result = PyToolkit::tryUnloadObject(fixture, addr.getAddress(), class_factory, nullptr, addr.getInstanceId()); if (!result.get()) { return false; } // validate type if requested - auto &memo = reinterpret_cast(result.get())->ext(); if (py_expected_type) { // in other cases the type must match the actual object type auto expected_class = class_factory.tryGetExistingType(py_expected_type); if (!expected_class) { return false; } - if (memo.getType() != *expected_class) { + if (PyToolkit::getMemoType(result.get()) != *expected_class) { return false; } } @@ -189,18 +191,17 @@ namespace db0::python // in other cases the type must match the actual object type auto expected_class = class_factory.getOrCreateType(py_expected_type); // honor class-specific access flags (e.g. type-level no_cache) - auto result = PyToolkit::unloadObject(fixture, addr, class_factory, nullptr, addr.getInstanceId(), + auto result = PyToolkit::unloadAnyObject(fixture, addr.getAddress(), class_factory, nullptr, addr.getInstanceId(), expected_class->getInstanceFlags() ); - auto &memo = reinterpret_cast(result.get())->ext(); // NOTE: base types should be accepted - if (!memo.getType().isBaseClass(*expected_class)) { + if (!PyToolkit::getMemoType(result.get()).isBaseClass(*expected_class)) { THROWF(db0::InputException) << "Object type mismatch"; } return result; } else { // unload without type validation - return PyToolkit::unloadObject(fixture, addr, class_factory, py_expected_type, addr.getInstanceId()); + return PyToolkit::unloadAnyObject(fixture, addr.getAddress(), class_factory, py_expected_type, addr.getInstanceId()); } } else if (storage_class == db0::object_model::StorageClass::DB0_CLASS) { auto &class_factory = db0::object_model::getClassFactory(*fixture); @@ -677,6 +678,9 @@ namespace db0::python PyObject *tryGetAddress(PyObject *py_obj) { + if (PyEmbeddedMemo_Check(py_obj)) { + return PyLong_FromUnsignedLongLong(getEmbeddedMemoAddress(py_obj).getValue()); + } if (PyAnyMemo_Check(py_obj)) { return PyLong_FromUnsignedLongLong( reinterpret_cast(py_obj)->ext().getAddress().getValue() diff --git a/src/dbzero/bindings/python/PyToolkit.cpp b/src/dbzero/bindings/python/PyToolkit.cpp index 7598dd52..c177a460 100644 --- a/src/dbzero/bindings/python/PyToolkit.cpp +++ b/src/dbzero/bindings/python/PyToolkit.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,22 @@ namespace db0::python return reinterpret_cast(pyObject)->ext().hasRefs(); } + PyToolkit::TypeObjectPtr resolveUnloadLangType( + const PyToolkit::ClassFactory &classFactory, const std::shared_ptr &type, + PyToolkit::TypeObjectSharedPtr langType, PyToolkit::TypeObjectPtr langTypeHint) + { + if (!langType) { + langType = classFactory.getLangType(*type); + } + if (!!langType) { + return langType.get(); + } + if (langTypeHint) { + return langTypeHint; + } + return PyToolkit::getTypeManager().getMemoBaseType().get(); + } + } PyToolkit::ObjectSharedPtr PyToolkit::unloadEmbeddedInstance( @@ -170,6 +187,9 @@ namespace db0::python bool PyToolkit::hasMemoInstance(ObjectPtr pyObject) { + if (PyEmbeddedMemo_Check(pyObject)) { + return true; + } if (PyMemo_Check(pyObject)) { return reinterpret_cast(pyObject)->ext().hasInstance(); } @@ -178,6 +198,9 @@ namespace db0::python UniqueAddress PyToolkit::getMemoUniqueAddress(ObjectPtr pyObject) { + if (PyEmbeddedMemo_Check(pyObject)) { + return getEmbeddedMemoUniqueAddress(pyObject); + } if (PyMemo_Check(pyObject)) { return reinterpret_cast(pyObject)->ext().getUniqueAddress(); } @@ -186,6 +209,9 @@ namespace db0::python bool PyToolkit::isMemoDead(ObjectPtr pyObject) { + if (PyEmbeddedMemo_Check(pyObject)) { + return false; + } if (PyMemo_Check(pyObject)) { return reinterpret_cast(pyObject)->ext().isDead(); } @@ -194,6 +220,9 @@ namespace db0::python bool PyToolkit::isMemoDropped(ObjectPtr pyObject) { + if (PyEmbeddedMemo_Check(pyObject)) { + return false; + } if (PyMemo_Check(pyObject)) { return reinterpret_cast(pyObject)->ext().isDropped(); } @@ -202,6 +231,9 @@ namespace db0::python bool PyToolkit::hasMemoAnyRefs(ObjectPtr pyObject) { + if (PyEmbeddedMemo_Check(pyObject)) { + return true; + } if (PyMemo_Check(pyObject)) { return reinterpret_cast(pyObject)->ext().hasAnyRefs(); } @@ -210,6 +242,9 @@ namespace db0::python const object_model::Class &PyToolkit::getMemoType(ObjectPtr pyObject) { + if (PyEmbeddedMemo_Check(pyObject)) { + return getEmbeddedMemoRef(reinterpret_cast(pyObject)).type(); + } if (PyMemo_Check(pyObject)) { return reinterpret_cast(pyObject)->ext().getType(); } @@ -430,14 +465,25 @@ namespace db0::python // NOTE: objects with no references (either from dbzero or other lang types) are considered deleted return PyToolkit::hasLangRefs(*obj_ptr) || memoHasRefs(obj_ptr.get()); } - - // Check if object's stem can be unloaded (and has refs) + + std::size_t sizeOf = 0; + if (!fixture->isAddressValid(address, db0::object_model::ObjectAnyImpl::REALM_ID, &sizeOf)) { + return false; + } + db0::object_model::ObjectAnyImpl::ObjectStem commonStem(db0::tag_verified(), fixture->myPtr(address), sizeOf); + if (instance_id && commonStem->m_header.getInstanceId() != instance_id) { + return false; + } + if (commonStem->m_header.isImmutableObject()) { + return db0::object_model::ObjectImmutableImpl::checkUnload(fixture, address, instance_id, true); + } return db0::object_model::Object::checkUnload(fixture, address, instance_id, true); } - - PyToolkit::ObjectSharedPtr PyToolkit::tryUnloadObject( - db0::swine_ptr &fixture, Address address, const ClassFactory &class_factory, - TypeObjectPtr lang_type_ptr, std::uint16_t instance_id, AccessFlags access_mode) + + static PyToolkit::ObjectSharedPtr tryUnloadObjectResolved( + db0::swine_ptr &fixture, Address address, const PyToolkit::ClassFactory &class_factory, + PyToolkit::TypeObjectPtr lang_type_ptr, std::uint16_t instance_id, AccessFlags access_mode, + const Allocator::AllocationInfo *allocationInfo) { // try unloading from cache first auto &lang_cache = fixture->getLangCache(); @@ -447,7 +493,7 @@ namespace db0::python // only validate instance ID if provided if (instance_id) { // NOTE: we first must check if this is really a memo object - if (!isAnyMemoObject(obj_ptr.get())) { + if (!PyToolkit::isAnyMemoObject(obj_ptr.get())) { return {}; } if (getMemoInstanceId(obj_ptr.get()) != instance_id) { @@ -458,43 +504,34 @@ namespace db0::python return obj_ptr; } - std::shared_ptr type; - shared_py_object lang_type; - auto immutableStem = [&]() { - try { - auto stem = db0::object_model::ObjectImmutableImpl::tryUnloadStem( - fixture, address, instance_id, access_mode - ); - if (!stem) { - return decltype(stem)(); - } - auto typeInfo = class_factory.getTypeByClassRef(stem->getClassRef()); - if (!typeInfo.m_class->isImmutable()) { - return decltype(stem)(); - } - type = typeInfo.m_class; - lang_type = typeInfo.m_lang_type; - return stem; - } catch (...) { - return db0::object_model::ObjectImmutableImpl::ObjectStem(); + std::size_t sizeOf; + if (allocationInfo) { + sizeOf = allocationInfo->size; + } else { + sizeOf = 0; + if (!fixture->isAddressValid(address, db0::object_model::ObjectAnyImpl::REALM_ID, &sizeOf)) { + return {}; } - }(); + } - if (!!immutableStem) { - if (!lang_type_ptr) { - if (!lang_type) { - lang_type = class_factory.getLangType(*type); - } - lang_type_ptr = lang_type.get(); - } + db0::object_model::ObjectAnyImpl::ObjectStem commonStem( + db0::tag_verified(), fixture->myPtr(address), sizeOf, access_mode + ); + if (instance_id && commonStem->m_header.getInstanceId() != instance_id) { + return {}; + } - if (!lang_type_ptr) { - lang_type_ptr = PyToolkit::getTypeManager().getMemoBaseType().get(); - } + if (commonStem->m_header.isImmutableObject()) { + auto stem = db0::object_model::ObjectAnyImpl::castStem< + db0::object_model::ObjectImmutableImpl::ObjectStem + >(std::move(commonStem)); + auto typeInfo = class_factory.getTypeByClassRef(stem->getClassRef()); + auto type = typeInfo.m_class; + lang_type_ptr = resolveUnloadLangType(class_factory, type, typeInfo.m_lang_type, lang_type_ptr); auto *memo_ptr = reinterpret_cast(lang_type_ptr->tp_alloc(lang_type_ptr, 0)); memo_ptr->unload( - fixture, std::move(immutableStem), type, db0::object_model::ObjectImmutableImpl::with_type_hint{} + fixture, std::move(stem), type, db0::object_model::ObjectImmutableImpl::with_type_hint{} ); memo_ptr->ext().setLangObject(reinterpret_cast(memo_ptr)); obj_ptr = Py_OWN(reinterpret_cast(memo_ptr)); @@ -504,35 +541,18 @@ namespace db0::python return obj_ptr; } - // Unload from backend otherwise - auto stem = db0::object_model::Object::tryUnloadStem( - fixture, address, instance_id, access_mode - ); - if (!stem) { - // object not found - return {}; - } + auto stem = db0::object_model::ObjectAnyImpl::castStem< + db0::object_model::Object::ObjectStem + >(std::move(commonStem)); auto typeInfo = class_factory.getTypeByClassRef(stem->getClassRef()); - type = typeInfo.m_class; - lang_type = typeInfo.m_lang_type; - - if (!lang_type_ptr) { - if (!lang_type) { - lang_type = class_factory.getLangType(*type); - } - lang_type_ptr = lang_type.get(); - } - - if (!lang_type_ptr) { - // set MemoBase as a fallback for objects without lang type (e.g. objects doesn't have imported class definition in the current workspace) - lang_type_ptr = PyToolkit::getTypeManager().getMemoBaseType().get(); - } + auto type = typeInfo.m_class; + lang_type_ptr = resolveUnloadLangType(class_factory, type, typeInfo.m_lang_type, lang_type_ptr); // construct Python's memo object (placeholder for actual dbzero instance) // the associated lang class must be available auto *memo_ptr = MemoObjectStub_new(lang_type_ptr); // unload from stem (with type hint) - memo_ptr->unload(fixture, std::move(stem), type, Object::with_type_hint{}); + memo_ptr->unload(fixture, std::move(stem), type, PyToolkit::Object::with_type_hint{}); // NOTE: Py_OWN only possible with a proper object obj_ptr = Py_OWN((PyObject*)memo_ptr); if (!memo_ptr->ext().isNoCache()) { @@ -540,7 +560,64 @@ namespace db0::python } return obj_ptr; } + + PyToolkit::ObjectSharedPtr PyToolkit::tryUnloadObject( + db0::swine_ptr &fixture, Address address, const ClassFactory &class_factory, + TypeObjectPtr lang_type_ptr, std::uint16_t instance_id, AccessFlags access_mode) + { + return tryUnloadObjectResolved( + fixture, address, class_factory, lang_type_ptr, instance_id, access_mode, nullptr + ); + } + PyToolkit::ObjectSharedPtr PyToolkit::unloadEmbeddedObject( + db0::swine_ptr &fixture, Address address, const PyToolkit::ClassFactory &class_factory, + PyToolkit::TypeObjectPtr lang_type_ptr, std::uint16_t instance_id, AccessFlags access_mode, + ObjectSharedPtr rootObject, const Allocator::AllocationInfo *allocationInfo) + { + Allocator::AllocationInfo alloc_info; + if (!allocationInfo) { + alloc_info = fixture->findAllocation(address, db0::object_model::ObjectImmutableImpl::REALM_ID); + allocationInfo = &alloc_info; + } + assert(allocationInfo); + auto embeddedOffset = address.getOffset() - allocationInfo->address.getOffset(); + + // Resolve the root object if not provided + if (!rootObject) { + rootObject = tryUnloadObjectResolved( + fixture, allocationInfo->address, class_factory, lang_type_ptr, instance_id, access_mode, + allocationInfo + ); + if (!rootObject) { + THROWF(db0::InputException) << "Invalid UUID or object has been deleted"; + } + } + assert(!!rootObject); + auto *rootMemo = reinterpret_cast(rootObject.get()); + return rootMemo->ext().getEmbeddedInstanceAtOffset(embeddedOffset); + } + + PyToolkit::ObjectSharedPtr PyToolkit::unloadAnyObject( + db0::swine_ptr &fixture, Address address, const ClassFactory &class_factory, + TypeObjectPtr lang_type_ptr, std::uint16_t instance_id, AccessFlags access_mode) + { + auto allocation = fixture->findAllocation(address, db0::object_model::ObjectImmutableImpl::REALM_ID); + auto rootObject = tryUnloadObjectResolved( + fixture, allocation.address, class_factory, lang_type_ptr, instance_id, access_mode, &allocation + ); + if (!rootObject) { + THROWF(db0::InputException) << "Invalid UUID or object has been deleted"; + } + if (allocation.address == address) { + return rootObject; + } + + return unloadEmbeddedObject( + fixture, address, class_factory, lang_type_ptr, instance_id, access_mode, rootObject, &allocation + ); + } + PyToolkit::ObjectSharedPtr PyToolkit::unloadObject(db0::swine_ptr &fixture, Address address, const ClassFactory &class_factory, TypeObjectPtr lang_type_ptr, std::uint16_t instance_id, AccessFlags access_mode) { @@ -893,7 +970,7 @@ namespace db0::python } bool PyToolkit::isAnyMemoObject(ObjectPtr py_object) { - return PyAnyMemo_Check(py_object); + return PyAnyMemo_Check(py_object) || PyEmbeddedMemo_Check(py_object); } bool PyToolkit::isMemoObject(ObjectPtr py_object) { @@ -901,7 +978,7 @@ namespace db0::python } bool PyToolkit::isMemoImmutableObject(ObjectPtr py_object) { - return PyMemo_Check(py_object); + return PyMemo_Check(py_object) || PyEmbeddedMemo_Check(py_object); } PyToolkit::ObjectPtr PyToolkit::getUUID(ObjectPtr py_object) { @@ -947,6 +1024,8 @@ namespace db0::python return reinterpret_cast(py_object)->ext().m_fixture.safe_lock()->getUUID(); } else if (PyAnyMemo_Check(py_object)) { return reinterpret_cast(py_object)->ext().getFixture()->getUUID(); + } else if (PyEmbeddedMemo_Check(py_object)) { + return getEmbeddedMemoFixture(py_object)->getUUID(); } else if (PyObjectIterable_Check(py_object)) { return reinterpret_cast(py_object)->ext().getFixture()->getUUID(); } else if (PyObjectIterator_Check(py_object)) { diff --git a/src/dbzero/bindings/python/PyToolkit.hpp b/src/dbzero/bindings/python/PyToolkit.hpp index 056a539d..9d2b653a 100644 --- a/src/dbzero/bindings/python/PyToolkit.hpp +++ b/src/dbzero/bindings/python/PyToolkit.hpp @@ -13,6 +13,7 @@ #include "PyLocks.hpp" #include "MemoObject.hpp" #include +#include #include #include @@ -107,6 +108,11 @@ namespace db0::python TypeObjectPtr lang_class = nullptr, std::uint16_t instance_id = 0, AccessFlags = {}); static ObjectSharedPtr tryUnloadObject(db0::swine_ptr &, Address, const ClassFactory &, TypeObjectPtr lang_class = nullptr, std::uint16_t instance_id = 0, AccessFlags = {}); + static ObjectSharedPtr unloadAnyObject(db0::swine_ptr &, Address, const ClassFactory &, + TypeObjectPtr lang_class = nullptr, std::uint16_t instance_id = 0, AccessFlags = {}); + static ObjectSharedPtr unloadEmbeddedObject(db0::swine_ptr &, Address, const ClassFactory &, + TypeObjectPtr lang_class = nullptr, std::uint16_t instance_id = 0, AccessFlags = {}, + ObjectSharedPtr root_object = {}, const Allocator::AllocationInfo *allocation_info = nullptr); static ObjectSharedPtr unloadObject(db0::swine_ptr &, Address, TypeObjectPtr lang_class = nullptr, std::uint16_t instance_id = 0, AccessFlags = {}); diff --git a/src/dbzero/bindings/python/PyTypeManager.cpp b/src/dbzero/bindings/python/PyTypeManager.cpp index af46c66e..6d441f82 100644 --- a/src/dbzero/bindings/python/PyTypeManager.cpp +++ b/src/dbzero/bindings/python/PyTypeManager.cpp @@ -7,6 +7,7 @@ #include #include "Memo.hpp" #include "PyWeakProxy.hpp" +#include #include #include #include @@ -140,6 +141,9 @@ namespace db0::python if (PyMemoType_Check(py_type)) { return TypeId::MEMO_IMMUTABLE_OBJECT; } + if (PyEmbeddedMemoType_Check(py_type)) { + return TypeId::MEMO_IMMUTABLE_OBJECT; + } // check with the static types next auto it = m_id_map.find(reinterpret_cast(py_type)); @@ -221,6 +225,39 @@ namespace db0::python } return reinterpret_cast(obj_ptr)->modifyExt(); } + + db0::swine_ptr PyTypeManager::extractObjectFixture(ObjectPtr obj_ptr) const + { + if (PyEmbeddedMemo_Check(obj_ptr)) { + return getEmbeddedMemoFixture(obj_ptr); + } + return extractAnyObject(obj_ptr).getFixture(); + } + + void PyTypeManager::validateForeignObjectReference(ObjectPtr obj_ptr) const + { + if (PyEmbeddedMemo_Check(obj_ptr)) { + THROWF(db0::InputException) + << "Embedded immutable object references cannot cross prefixes"; + } + } + + db0::UniqueAddress PyTypeManager::extractObjectUniqueAddress(ObjectPtr obj_ptr) const + { + if (PyEmbeddedMemo_Check(obj_ptr)) { + return getEmbeddedMemoUniqueAddress(obj_ptr); + } + return extractAnyObject(obj_ptr).getUniqueAddress(); + } + + void PyTypeManager::incObjectRef(ObjectPtr obj_ptr) const + { + if (PyEmbeddedMemo_Check(obj_ptr)) { + incEmbeddedMemoRef(obj_ptr, false); + return; + } + extractMutableAnyObject(obj_ptr).incRef(false); + } template typename MemoImplT::ExtT & PyTypeManager::extractMutableObject(ObjectPtr obj_ptr) const diff --git a/src/dbzero/bindings/python/PyTypeManager.hpp b/src/dbzero/bindings/python/PyTypeManager.hpp index e95ba951..9bef97e4 100644 --- a/src/dbzero/bindings/python/PyTypeManager.hpp +++ b/src/dbzero/bindings/python/PyTypeManager.hpp @@ -14,6 +14,8 @@ #include "PyTypes.hpp" #include #include +#include +#include #include "MemoObject.hpp" namespace db0 @@ -21,6 +23,7 @@ namespace db0 { class ProcessTimer; + class Fixture; } @@ -130,6 +133,10 @@ namespace db0::python // Extracts reference to common object part from a memo instance const ObjectAnyImpl &extractAnyObject(ObjectPtr) const; ObjectAnyImpl &extractMutableAnyObject(ObjectPtr) const; + db0::swine_ptr extractObjectFixture(ObjectPtr) const; + void validateForeignObjectReference(ObjectPtr) const; + db0::UniqueAddress extractObjectUniqueAddress(ObjectPtr) const; + void incObjectRef(ObjectPtr) const; const ObjectAnyImpl *tryExtractObject(ObjectPtr memo_ptr) const; diff --git a/src/dbzero/bindings/python/Types.cpp b/src/dbzero/bindings/python/Types.cpp index 5a0c4c7e..32f510d9 100644 --- a/src/dbzero/bindings/python/Types.cpp +++ b/src/dbzero/bindings/python/Types.cpp @@ -7,6 +7,7 @@ #include "MemoExpiredRef.hpp" #include "PyAPI.hpp" #include "PyInternalAPI.hpp" +#include #include #include #include @@ -34,6 +35,9 @@ namespace db0::python // IMMUTABLE OBJECT specialization template <> db0::swine_ptr getFixtureOf(PyObject *py_value) { + if (PyEmbeddedMemo_Check(py_value)) { + return getEmbeddedMemoFixture(py_value); + } return reinterpret_cast(py_value)->ext().getFixture(); } @@ -147,6 +151,16 @@ namespace db0::python // IMMUTABLE OBJECT specialization template <> PyObject *tryGetUUID(PyObject *py_value) { + if (PyEmbeddedMemo_Check(py_value)) { + db0::object_model::ObjectId object_id; + object_id.m_fixture_uuid = getEmbeddedMemoFixture(py_value)->getUUID(); + object_id.m_address = getEmbeddedMemoUniqueAddress(py_value); + object_id.m_storage_class = getStorageClass(); + + char buffer[ObjectId::maxEncodedSize() + 1]; + object_id.toBase32(buffer); + return PyUnicode_FromString(buffer); + } return tryGetUUIDOf(reinterpret_cast(py_value)); } diff --git a/src/dbzero/bindings/python/collections/PyIndex.cpp b/src/dbzero/bindings/python/collections/PyIndex.cpp index 9b6912ba..36aefd84 100644 --- a/src/dbzero/bindings/python/collections/PyIndex.cpp +++ b/src/dbzero/bindings/python/collections/PyIndex.cpp @@ -192,6 +192,9 @@ namespace db0::python PyObject *tryIndexObject_range(IndexObject *py_index, PyObject *args, PyObject *kwargs) { + using ObjectIterable = db0::object_model::ObjectIterable; + using QueryObserver = db0::object_model::QueryObserver; + // optional low, optional high, optional null_first (boolean) static const char *kwlist[] = {"low", "high", "null_first", NULL}; PyObject *low = NULL, *high = NULL; @@ -204,7 +207,10 @@ namespace db0::python // construct range iterator auto iter_factory = index.range(low, high, null_first); auto py_iter_obj = PyObjectIterableDefault_new(); - py_iter_obj->makeNew(index.getFixture(), std::move(iter_factory)); + py_iter_obj->makeNew( + index.getFixture(), std::move(iter_factory), nullptr, nullptr, std::vector >{}, + std::vector{} + ); return py_iter_obj.steal(); } diff --git a/src/dbzero/bindings/python/embedded/EmbeddedObject.cpp b/src/dbzero/bindings/python/embedded/EmbeddedObject.cpp index 73792e86..8825f378 100644 --- a/src/dbzero/bindings/python/embedded/EmbeddedObject.cpp +++ b/src/dbzero/bindings/python/embedded/EmbeddedObject.cpp @@ -72,11 +72,37 @@ namespace db0::python return *m_type; } + db0::swine_ptr EmbeddedObjectRef::fixture() const + { + return reinterpret_cast(m_root_object)->ext().getFixture(); + } + + std::uint64_t EmbeddedObjectRef::offset() const + { + const auto *root = reinterpret_cast( + reinterpret_cast(m_root_object)->ext().operator->() + ); + const auto *embedded = reinterpret_cast(m_embedded_object); + assert(root <= embedded); + return static_cast(embedded - root); + } + + db0::Address EmbeddedObjectRef::address() const + { + return reinterpret_cast(m_root_object)->ext().getAddress() + offset(); + } + + db0::UniqueAddress EmbeddedObjectRef::uniqueAddress() const + { + auto rootUniqueAddress = reinterpret_cast(m_root_object)->ext().getUniqueAddress(); + return db0::UniqueAddress(address(), rootUniqueAddress.getInstanceId()); + } + namespace { EmbeddedObjectRef &embeddedMemoRef(MemoImmutableObject *object) { - return *reinterpret_cast(const_cast(&object->ext())); + return getEmbeddedMemoRef(object); } db0::swine_ptr getRootFixture(PyObject *rootObject) @@ -329,6 +355,29 @@ namespace db0::python return hash == -1 ? -2 : hash; } + PyObject *tryEmbeddedMemoRichCompare(MemoImmutableObject *self, PyObject *other, int op) + { + if (op != Py_EQ && op != Py_NE) { + Py_RETURN_NOTIMPLEMENTED; + } + + bool isEqual = false; + if (PyEmbeddedMemo_Check(other)) { + auto &lhs = embeddedMemoRef(self); + auto &rhs = embeddedMemoRef(reinterpret_cast(other)); + isEqual = lhs.fixture()->getUUID() == rhs.fixture()->getUUID() + && lhs.uniqueAddress() == rhs.uniqueAddress(); + } + + return PyBool_fromBool(op == Py_EQ ? isEqual : !isEqual); + } + + PyObject *PyAPI_EmbeddedMemo_richcompare(MemoImmutableObject *self, PyObject *other, int op) + { + PY_API_FUNC + return runSafe(tryEmbeddedMemoRichCompare, self, other, op); + } + static PyMethodDef EmbeddedMemo_methods[] = { {"__dir__", (PyCFunction)PyAPI_EmbeddedMemo_dir, METH_NOARGS, nullptr}, {NULL} @@ -350,6 +399,7 @@ namespace db0::python {Py_tp_methods, reinterpret_cast(EmbeddedMemo_methods)}, {Py_tp_getset, reinterpret_cast(EmbeddedMemo_getsets)}, {Py_tp_hash, reinterpret_cast(PyAPI_EmbeddedMemo_hash)}, + {Py_tp_richcompare, reinterpret_cast(PyAPI_EmbeddedMemo_richcompare)}, {Py_tp_repr, reinterpret_cast(PyAPI_EmbeddedMemo_str)}, {Py_tp_str, reinterpret_cast(PyAPI_EmbeddedMemo_str)}, {0, 0} @@ -694,4 +744,39 @@ namespace db0::python { return object && PyEmbeddedMemoType_Check(Py_TYPE(object)); } + + EmbeddedObjectRef &getEmbeddedMemoRef(MemoImmutableObject *object) + { + return *reinterpret_cast(const_cast(&object->ext())); + } + + const EmbeddedObjectRef &getEmbeddedMemoRef(const MemoImmutableObject *object) + { + return *reinterpret_cast(&object->ext()); + } + + db0::swine_ptr getEmbeddedMemoFixture(PyObject *object) + { + assert(PyEmbeddedMemo_Check(object)); + return getEmbeddedMemoRef(reinterpret_cast(object)).fixture(); + } + + db0::Address getEmbeddedMemoAddress(PyObject *object) + { + assert(PyEmbeddedMemo_Check(object)); + return getEmbeddedMemoRef(reinterpret_cast(object)).address(); + } + + db0::UniqueAddress getEmbeddedMemoUniqueAddress(PyObject *object) + { + assert(PyEmbeddedMemo_Check(object)); + return getEmbeddedMemoRef(reinterpret_cast(object)).uniqueAddress(); + } + + void incEmbeddedMemoRef(PyObject *object, bool isTag) + { + assert(PyEmbeddedMemo_Check(object)); + auto *rootObject = getEmbeddedMemoRef(reinterpret_cast(object)).rootObject(); + reinterpret_cast(rootObject)->modifyExt().incRef(isTag); + } } diff --git a/src/dbzero/bindings/python/embedded/EmbeddedObject.hpp b/src/dbzero/bindings/python/embedded/EmbeddedObject.hpp index 81ffc6e9..ed2b23a5 100644 --- a/src/dbzero/bindings/python/embedded/EmbeddedObject.hpp +++ b/src/dbzero/bindings/python/embedded/EmbeddedObject.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace db0 @@ -43,6 +44,10 @@ namespace db0::python PyObject *rootObject() const; const db0::object_model::o_embedded_object &embeddedObject() const; db0::object_model::Class &type() const; + db0::swine_ptr fixture() const; + db0::Address address() const; + db0::UniqueAddress uniqueAddress() const; + std::uint64_t offset() const; private: PyObject *m_root_object = nullptr; @@ -91,4 +96,10 @@ namespace db0::python bool PyEmbeddedMemo_Check(PyObject *object); bool PyEmbeddedMemoType_Check(PyTypeObject *type); + EmbeddedObjectRef &getEmbeddedMemoRef(MemoImmutableObject *object); + const EmbeddedObjectRef &getEmbeddedMemoRef(const MemoImmutableObject *object); + db0::swine_ptr getEmbeddedMemoFixture(PyObject *object); + db0::Address getEmbeddedMemoAddress(PyObject *object); + db0::UniqueAddress getEmbeddedMemoUniqueAddress(PyObject *object); + void incEmbeddedMemoRef(PyObject *object, bool isTag); } diff --git a/src/dbzero/core/memory/Memspace.cpp b/src/dbzero/core/memory/Memspace.cpp index e6bf26be..d7c210c5 100644 --- a/src/dbzero/core/memory/Memspace.cpp +++ b/src/dbzero/core/memory/Memspace.cpp @@ -162,5 +162,11 @@ namespace db0 assert(m_allocator_ptr); return m_allocator_ptr->isAllocated(address, realm_id, size_of_result); } + + Allocator::AllocationInfo Memspace::findAllocation(Address address, unsigned char realm_id) const + { + assert(m_allocator_ptr); + return m_allocator_ptr->findAllocation(address, realm_id); + } } diff --git a/src/dbzero/core/memory/Memspace.hpp b/src/dbzero/core/memory/Memspace.hpp index 106d1f5f..77410d66 100644 --- a/src/dbzero/core/memory/Memspace.hpp +++ b/src/dbzero/core/memory/Memspace.hpp @@ -111,6 +111,7 @@ namespace db0 // Check if the address is valid (allocated) with the underlying allocator // and retrieve the allocation size (on request) bool isAddressValid(Address, unsigned char realm_id, std::size_t *size_of_result = nullptr) const; + Allocator::AllocationInfo findAllocation(Address address, unsigned char realm_id) const; // Calcuate page number for a specific address (not validated) inline std::uint64_t getPageNum(Address address) const { diff --git a/src/dbzero/object_model/ObjectBase.hpp b/src/dbzero/object_model/ObjectBase.hpp index e4ab16a0..0e1847a3 100644 --- a/src/dbzero/object_model/ObjectBase.hpp +++ b/src/dbzero/object_model/ObjectBase.hpp @@ -156,7 +156,7 @@ namespace db0 UniqueAddress getUniqueAddress() const { assert(hasInstance()); - return UniqueAddress(this->getAddress(), (*this)->m_header.m_instance_id); + return UniqueAddress(this->getAddress(), (*this)->m_header.getInstanceId()); } // Get unique object's instance ID @@ -164,7 +164,7 @@ namespace db0 std::uint16_t getInstanceId() const { assert(hasInstance()); - return (*this)->m_header.m_instance_id; + return (*this)->m_header.getInstanceId(); } static bool checkUnload(db0::swine_ptr &fixture, Address address) { @@ -186,7 +186,7 @@ namespace db0 if (instance_id || check_has_refs) { // NOTE: here we use trusted size_of retrieved from the allocator auto stem = BaseT(db0::tag_verified(), fixture->myPtr(address), size_of); - if (instance_id && stem->m_header.m_instance_id == instance_id) { + if (instance_id && stem->m_header.getInstanceId() != instance_id) { return false; } if (check_has_refs && !stem->hasRefs()) { @@ -219,7 +219,7 @@ namespace db0 assert(fixture); if constexpr (Unique) { auto instance_id = has_fixture::initUnique(fixture, std::forward(args)...); - this->modify().m_header.m_instance_id = instance_id; + this->modify().m_header.setInstanceId(instance_id); } else { has_fixture::init(fixture, std::forward(args)...); } diff --git a/src/dbzero/object_model/index/IndexBuilder.hpp b/src/dbzero/object_model/index/IndexBuilder.hpp index 3a047ea3..d398c7f0 100644 --- a/src/dbzero/object_model/index/IndexBuilder.hpp +++ b/src/dbzero/object_model/index/IndexBuilder.hpp @@ -95,7 +95,7 @@ namespace db0::object_model std::function add_callback = [&](UniqueAddress address) { auto it = m_object_cache.find(address); assert(it != m_object_cache.end()); - m_type_manager.extractMutableAnyObject(it->second.get()).incRef(false); + m_type_manager.incObjectRef(it->second.get()); }; std::function erase_callback = [&](UniqueAddress address) { @@ -111,11 +111,11 @@ namespace db0::object_model template UniqueAddress IndexBuilder::addToCache(ObjectPtr obj_ptr) { - auto obj_addr = m_type_manager.extractAnyObject(obj_ptr).getUniqueAddress(); + auto obj_addr = m_type_manager.extractObjectUniqueAddress(obj_ptr); if (m_object_cache.find(obj_addr) == m_object_cache.end()) { m_object_cache.emplace(obj_addr, obj_ptr); } return obj_addr; } -} \ No newline at end of file +} diff --git a/src/dbzero/object_model/object/ObjectAnyImpl.hpp b/src/dbzero/object_model/object/ObjectAnyImpl.hpp index 18da05e5..dcbf5f2b 100644 --- a/src/dbzero/object_model/object/ObjectAnyImpl.hpp +++ b/src/dbzero/object_model/object/ObjectAnyImpl.hpp @@ -6,6 +6,8 @@ #include "ObjectAnyBase.hpp" #include "o_object.hpp" +#include + namespace db0::object_model { @@ -17,6 +19,15 @@ namespace db0::object_model public: static constexpr unsigned char REALM_ID = o_object_base::REALM_ID; using super_t = ObjectAnyBase; + + template + static StemT castStem(ObjectStem &&stem) + { + static_assert(sizeof(StemT) == sizeof(ObjectStem), "Object stem cast requires identical stem size"); + static_assert(alignof(StemT) == alignof(ObjectStem), "Object stem cast requires identical stem alignment"); + static_assert(StemT::getRealmID() == REALM_ID, "Object stem cast requires a common object realm"); + return StemT(std::move(reinterpret_cast(stem))); + } protected: friend super_t; diff --git a/src/dbzero/object_model/object/ObjectImmutableImpl.cpp b/src/dbzero/object_model/object/ObjectImmutableImpl.cpp index 03cd0011..b218657b 100644 --- a/src/dbzero/object_model/object/ObjectImmutableImpl.cpp +++ b/src/dbzero/object_model/object/ObjectImmutableImpl.cpp @@ -300,7 +300,7 @@ namespace db0::object_model std::uint64_t offset ) const { - if (!this->hasInstance() || !(*this)->embeddedObjectOffsets().contains(offset)) { + if (!this->hasInstance() || !(*this)->getOffsetIndex().contains(offset)) { THROWF(db0::BadAddressException) << "Invalid embedded immutable object offset: " << offset; } @@ -446,7 +446,7 @@ namespace db0::object_model void ObjectImmutableImpl::dropMembers(db0::swine_ptr &fixture, Class &classRef) const { super_t::dropMembers(fixture, classRef); - unrefEmbeddedObject(fixture, (*this)->embeddedObject()); + unrefEmbeddedObject(fixture, (*this)->getObject()); } } diff --git a/src/dbzero/object_model/object/ObjectImplBase.cpp b/src/dbzero/object_model/object/ObjectImplBase.cpp index 4752d9c9..21b47982 100644 --- a/src/dbzero/object_model/object/ObjectImplBase.cpp +++ b/src/dbzero/object_model/object/ObjectImplBase.cpp @@ -141,7 +141,7 @@ namespace db0::object_model } // Unload from a verified address ObjectStem stem(db0::tag_verified(), fixture->myPtr(address), size_of, access_mode); - if (instance_id && stem->m_header.m_instance_id != instance_id) { + if (instance_id && stem->m_header.getInstanceId() != instance_id) { // instance ID validation failed return {}; } diff --git a/src/dbzero/object_model/object/o_immutable_object.cpp b/src/dbzero/object_model/object/o_immutable_object.cpp index f9bf19cf..9912e690 100644 --- a/src/dbzero/object_model/object/o_immutable_object.cpp +++ b/src/dbzero/object_model/object/o_immutable_object.cpp @@ -359,14 +359,16 @@ namespace db0::object_model (IndexVT::type(), indexVtData.first, indexVtData.second) (o_dict::type(), fieldMap); } + } o_immutable_object::o_immutable_object(std::uint32_t class_ref, std::pair ref_counts, std::uint8_t num_type_tags, const ImmutableObjectInitializer &initializer) - : m_header(ref_counts) + : super_t(ref_counts) , m_num_type_tags(num_type_tags) { + m_header.setImmutableObject(); std::vector offsets; EmbeddedObjectOffsetCollector offsetCollector{ reinterpret_cast(this), &offsets }; auto arranger = arrangeMembers(); @@ -377,81 +379,82 @@ namespace db0::object_model o_immutable_object::o_immutable_object(std::uint32_t class_ref, std::pair ref_counts, std::uint8_t num_type_tags, const PosVT::Data &pos_vt_data, unsigned int pos_vt_offset, const XValue *index_vt_begin, const XValue *index_vt_end) - : m_header(ref_counts) + : super_t(ref_counts) , m_num_type_tags(num_type_tags) { + m_header.setImmutableObject(); auto arranger = arrangeMembers(); arranger = arranger(o_embedded_object::type(), class_ref, pos_vt_data, pos_vt_offset, index_vt_begin, index_vt_end); arranger(o_packed_offset_index::type(), std::vector()); } std::size_t o_immutable_object::measure(std::uint32_t class_ref, - std::pair, std::uint8_t, const ImmutableObjectInitializer &initializer) + std::pair ref_counts, std::uint8_t, const ImmutableObjectInitializer &initializer) { MeasureScratch scratch; auto offsets = worstCaseOffsetIndexValues(countEmbeddedMemoObjectsInInitializer(initializer)); - return super_t::measureMembers() + return super_t::measureMembersFromBase(ref_counts) (measureEmbeddedObjectNoWriters(class_ref, initializer, scratch)) (o_packed_offset_index::type(), offsets); } - + std::size_t o_immutable_object::measure(std::uint32_t class_ref, - std::pair, std::uint8_t, const PosVT::Data &pos_vt_data, unsigned int pos_vt_offset, + std::pair ref_counts, std::uint8_t, const PosVT::Data &pos_vt_data, unsigned int pos_vt_offset, const XValue *index_vt_begin, const XValue *index_vt_end) { - return super_t::measureMembers() + return super_t::measureMembersFromBase(ref_counts) (o_embedded_object::type(), class_ref, pos_vt_data, pos_vt_offset, index_vt_begin, index_vt_end) (o_packed_offset_index::type(), std::vector()); } - o_embedded_object &o_immutable_object::embeddedObject() + o_embedded_object &o_immutable_object::getObject() { return getDynFirst(o_embedded_object::type()); } - const o_embedded_object &o_immutable_object::embeddedObject() const + const o_embedded_object &o_immutable_object::getObject() const { return getDynFirst(o_embedded_object::type()); } - const o_packed_offset_index &o_immutable_object::embeddedObjectOffsets() const + const o_packed_offset_index &o_immutable_object::getOffsetIndex() const { - return getDynAfter(embeddedObject(), o_packed_offset_index::type()); + return getDynAfter(getObject(), o_packed_offset_index::type()); } const PosVT &o_immutable_object::pos_vt() const { - return embeddedObject().pos_vt(); + return getObject().pos_vt(); } PosVT &o_immutable_object::pos_vt() { - return embeddedObject().pos_vt(); + return getObject().pos_vt(); } std::uint32_t o_immutable_object::getClassRef() const { - return embeddedObject().getClassRef(); + return getObject().getClassRef(); } const IndexVT &o_immutable_object::index_vt() const { - return embeddedObject().index_vt(); + return getObject().index_vt(); } IndexVT &o_immutable_object::index_vt() { - return embeddedObject().index_vt(); + return getObject().index_vt(); } const o_dict &o_immutable_object::field_map() const { - return embeddedObject().field_map(); + return getObject().field_map(); } std::optional o_immutable_object::fixedValue(std::uint32_t index, unsigned int fidelityOffset) const { - return embeddedObject().fixedValue(index, fidelityOffset); + return getObject().fixedValue(index, fidelityOffset); } const o_tuple_item *o_immutable_object::variableValue(std::uint32_t index) const { - return embeddedObject().variableValue(index); + return getObject().variableValue(index); } void o_immutable_object::incRef(bool is_tag) { diff --git a/src/dbzero/object_model/object/o_immutable_object.hpp b/src/dbzero/object_model/object/o_immutable_object.hpp index 8b2c7424..c9e40916 100644 --- a/src/dbzero/object_model/object/o_immutable_object.hpp +++ b/src/dbzero/object_model/object/o_immutable_object.hpp @@ -5,6 +5,7 @@ #include #include +#include "o_object.hpp" #include "o_embedded_object.hpp" #include "o_packed_offset_index.hpp" #include @@ -14,21 +15,19 @@ namespace db0::object_model { DB0_PACKED_BEGIN - class DB0_PACKED_ATTR o_immutable_object: public db0::o_base + class DB0_PACKED_ATTR o_immutable_object: public db0::o_ext { protected: - using super_t = db0::o_base; + using super_t = db0::o_ext; public: static constexpr unsigned char REALM_ID = 1; - // common object header - o_unique_header m_header; // number of auto-assigned type tags std::uint8_t m_num_type_tags = 0; - o_embedded_object &embeddedObject(); - const o_embedded_object &embeddedObject() const; - const o_packed_offset_index &embeddedObjectOffsets() const; + o_embedded_object &getObject(); + const o_embedded_object &getObject() const; + const o_packed_offset_index &getOffsetIndex() const; PosVT &pos_vt(); const PosVT &pos_vt() const; @@ -66,14 +65,14 @@ DB0_PACKED_BEGIN (o_packed_offset_index::type()); } - static constexpr std::size_t dynamicOffset() + static std::size_t dynamicOffset() { - return super_t::baseSize(); + return super_t::measureBase(std::pair()); } static std::size_t measureBaseWithEmbeddedSize(std::size_t embeddedSize) { - return super_t::baseSize() + embeddedSize; + return dynamicOffset() + embeddedSize; } void incRef(bool is_tag); diff --git a/src/dbzero/object_model/object/o_object.cpp b/src/dbzero/object_model/object/o_object.cpp index 3aac27a0..523f8ab4 100644 --- a/src/dbzero/object_model/object/o_object.cpp +++ b/src/dbzero/object_model/object/o_object.cpp @@ -88,4 +88,4 @@ namespace db0::object_model return m_header.m_ref_counter.getSecond() > 0; } -} \ No newline at end of file +} diff --git a/src/dbzero/object_model/object/o_object.hpp b/src/dbzero/object_model/object/o_object.hpp index dfdc4a5d..b17ce9d5 100644 --- a/src/dbzero/object_model/object/o_object.hpp +++ b/src/dbzero/object_model/object/o_object.hpp @@ -85,4 +85,4 @@ DB0_PACKED_BEGIN }; DB0_PACKED_END -} \ No newline at end of file +} diff --git a/src/dbzero/object_model/object_header.hpp b/src/dbzero/object_model/object_header.hpp index e44655bf..375b3377 100644 --- a/src/dbzero/object_model/object_header.hpp +++ b/src/dbzero/object_model/object_header.hpp @@ -3,6 +3,7 @@ #pragma once +#include #include #include #include @@ -49,7 +50,12 @@ DB0_PACKED_BEGIN // Unique header for objects with unique instance id struct DB0_PACKED_ATTR o_unique_header: public o_fixed_ext { - // instance ID is decoded from object's address (see. db0::getInstanceId) + static constexpr std::uint16_t INSTANCE_ID_MASK = 0x3fff; + static constexpr std::uint16_t RESERVED_FLAG = 0x4000; + static constexpr std::uint16_t IMMUTABLE_FLAG = 0x8000; + // instance ID is decoded from object's address (see. db0::getInstanceId). + // The top 2 bits are not part of the instance ID; one stores the immutable-object flag + // and the other is intentionally left unused for a future object-header flag. std::uint16_t m_instance_id = 0; o_unique_header() = default; @@ -62,7 +68,28 @@ DB0_PACKED_BEGIN : o_fixed_ext(ref_counts) { } + + inline std::uint16_t getInstanceId() const + { + return m_instance_id & INSTANCE_ID_MASK; + } + + inline void setInstanceId(std::uint16_t instance_id) + { + assert((instance_id & ~INSTANCE_ID_MASK) == 0); + m_instance_id = (m_instance_id & ~INSTANCE_ID_MASK) | instance_id; + } + + inline bool isImmutableObject() const + { + return (m_instance_id & IMMUTABLE_FLAG) != 0; + } + + inline void setImmutableObject() + { + m_instance_id |= IMMUTABLE_FLAG; + } }; DB0_PACKED_END -} \ No newline at end of file +} diff --git a/src/dbzero/object_model/tags/ObjectIterable.hpp b/src/dbzero/object_model/tags/ObjectIterable.hpp index 1f9e9474..bfa4c171 100644 --- a/src/dbzero/object_model/tags/ObjectIterable.hpp +++ b/src/dbzero/object_model/tags/ObjectIterable.hpp @@ -47,7 +47,7 @@ namespace db0::object_model // a common base for full-text and sorted iterators using BaseIterator = db0::FT_IteratorBase; using FilterFunc = std::function; - + ObjectIterable(ObjectIterable &&) = default; // Construct from a full-text query iterator @@ -167,4 +167,4 @@ namespace db0::object_model void getItemsByIndices(const ObjectIterable &, const std::vector &, std::function); -} \ No newline at end of file +} diff --git a/src/dbzero/object_model/tags/ObjectIterator.cpp b/src/dbzero/object_model/tags/ObjectIterator.cpp index 300cead0..0ab035e0 100644 --- a/src/dbzero/object_model/tags/ObjectIterator.cpp +++ b/src/dbzero/object_model/tags/ObjectIterator.cpp @@ -90,25 +90,12 @@ namespace db0::object_model return skipped; } - ObjectIterator::ObjectSharedPtr ObjectIterator::unload(db0::swine_ptr &fixture, Address address) const - { - // Unload with type and lang type hints - if (m_type && m_lang_type.get()) { - return LangToolkit::unloadObject( - fixture, address, m_type, m_lang_type.get(), m_access_mode, &m_class_factory - ); - } else { - // Memo type or lang type is missing. We try to resolve both here. - return LangToolkit::unloadObject( - fixture, address, m_class_factory, m_lang_type.get(), 0, m_access_mode - ); - } - } - ObjectIterator::ObjectSharedPtr ObjectIterator::unload(Address address) const { auto fixture = getFixture(); - return unload(fixture, address); + return LangToolkit::unloadAnyObject( + fixture, address, m_class_factory, m_lang_type.get(), 0, m_access_mode + ); } } diff --git a/src/dbzero/object_model/tags/ObjectIterator.hpp b/src/dbzero/object_model/tags/ObjectIterator.hpp index be66e28a..9a691008 100644 --- a/src/dbzero/object_model/tags/ObjectIterator.hpp +++ b/src/dbzero/object_model/tags/ObjectIterator.hpp @@ -91,9 +91,8 @@ namespace db0::object_model Decoration m_decoration; Slice m_slice; - // Unload object by address (must be from this iterator) skipping instance ID validation + // Unload object by address (must be from this iterator) skipping instance ID validation virtual ObjectSharedPtr unload(Address) const; - ObjectSharedPtr unload(db0::swine_ptr &, Address) const; }; -} \ No newline at end of file +} diff --git a/src/dbzero/object_model/value/Member.cpp b/src/dbzero/object_model/value/Member.cpp index 397bdbb3..35a793e1 100644 --- a/src/dbzero/object_model/value/Member.cpp +++ b/src/dbzero/object_model/value/Member.cpp @@ -16,9 +16,9 @@ // FIXME: remove Python dependency #include #include +#include #include #include -#include namespace db0::object_model @@ -85,6 +85,8 @@ namespace db0::object_model return db0::v_object(*fixture, PyUnicode_AsUTF8(obj_ptr), access_mode).getAddress(); } + struct EmbeddedMemoObject {}; + // OBJECT specialization (mutable or immutable) template Value createObjectMember(db0::swine_ptr &fixture, PyObjectPtr obj_ptr, StorageClass storage_class, AccessFlags access_flags) @@ -96,6 +98,29 @@ namespace db0::object_model return resolveForFixture(fixture, obj, obj_ptr, storage_class, access_flags, false); } + template <> Value createObjectMember(db0::swine_ptr &fixture, + PyObjectPtr obj_ptr, StorageClass, AccessFlags) + { + assert(db0::python::PyEmbeddedMemo_Check(obj_ptr)); + auto embeddedFixture = db0::python::getEmbeddedMemoFixture(obj_ptr); + if (*embeddedFixture != *fixture) { + THROWF(db0::InputException) + << "Embedded immutable object references cannot cross prefixes"; + } + db0::python::incEmbeddedMemoRef(obj_ptr, false); + return db0::python::getEmbeddedMemoUniqueAddress(obj_ptr); + } + + Value createMemoImmutableObjectMember(db0::swine_ptr &fixture, + PyObjectPtr obj_ptr, StorageClass storage_class, AccessFlags access_flags) + { + using MemoImmutableObject = PyToolkit::TypeManager::MemoImmutableObject; + if (db0::python::PyEmbeddedMemo_Check(obj_ptr)) { + return createObjectMember(fixture, obj_ptr, storage_class, access_flags); + } + return createObjectMember(fixture, obj_ptr, storage_class, access_flags); + } + // LIST specialization template <> Value createMember(db0::swine_ptr &fixture, PyObjectPtr obj_ptr, StorageClass storage_class, AccessFlags access_flags) @@ -356,7 +381,6 @@ namespace db0::object_model std::vector &, PyObjectPtr, StorageClass, AccessFlags)> &functions) { using MemoObject = PyToolkit::TypeManager::MemoObject; - using MemoImmutableObject = PyToolkit::TypeManager::MemoImmutableObject; functions.resize(static_cast(TypeId::COUNT)); std::fill(functions.begin(), functions.end(), nullptr); @@ -365,7 +389,7 @@ namespace db0::object_model functions[static_cast(TypeId::FLOAT)] = createMember; functions[static_cast(TypeId::STRING)] = createMember; functions[static_cast(TypeId::MEMO_OBJECT)] = createObjectMember; - functions[static_cast(TypeId::MEMO_IMMUTABLE_OBJECT)] = createObjectMember; + functions[static_cast(TypeId::MEMO_IMMUTABLE_OBJECT)] = createMemoImmutableObjectMember; functions[static_cast(TypeId::DB0_LIST)] = createMember; functions[static_cast(TypeId::DB0_INDEX)] = createMember; functions[static_cast(TypeId::DB0_SET)] = createMember; @@ -427,6 +451,17 @@ namespace db0::object_model auto &class_factory = fixture->template get(); return PyToolkit::unloadObject(fixture, value.asAddress(), class_factory); } + + // EMBEDDED_OBJECT_REF specialization + template <> typename PyToolkit::ObjectSharedPtr unloadMember( + db0::swine_ptr &fixture, Value value, unsigned int, AccessFlags access_mode) + { + auto embeddedAddress = value.asUniqueAddress(); + auto &classFactory = fixture->template get(); + return PyToolkit::unloadEmbeddedObject( + fixture, embeddedAddress.getAddress(), classFactory, nullptr, embeddedAddress.getInstanceId(), access_mode + ); + } // DB0_LIST specialization template <> typename PyToolkit::ObjectSharedPtr unloadMember( @@ -668,6 +703,7 @@ namespace db0::object_model functions[static_cast(StorageClass::FP_NUMERIC64)] = unloadMember; functions[static_cast(StorageClass::STRING_REF)] = unloadMember; functions[static_cast(StorageClass::OBJECT_REF)] = unloadMember; + functions[static_cast(StorageClass::EMBEDDED_OBJECT_REF)] = unloadMember; functions[static_cast(StorageClass::DB0_LIST)] = unloadMember; functions[static_cast(StorageClass::DB0_INDEX)] = unloadMember; functions[static_cast(StorageClass::DB0_SET)] = unloadMember; @@ -754,6 +790,13 @@ namespace db0::object_model using MemoObject = PyToolkit::TypeManager::MemoObject; unrefMemoObject(fixture, value.asAddress()); } + + template <> void unrefMember( + db0::swine_ptr &, Value) + { + // Embedded immutable references point inside the root allocation. Retrieval is implemented + // for this feature slice; unreferencing embedded references is intentionally deferred. + } template <> void unrefMember( db0::swine_ptr &fixture, Value value) @@ -823,6 +866,7 @@ namespace db0::object_model functions.resize(static_cast(StorageClass::COUNT)); std::fill(functions.begin(), functions.end(), nullptr); functions[static_cast(StorageClass::OBJECT_REF)] = unrefMember; + functions[static_cast(StorageClass::EMBEDDED_OBJECT_REF)] = unrefMember; functions[static_cast(StorageClass::DB0_LIST)] = unrefMember; functions[static_cast(StorageClass::DB0_INDEX)] = unrefMember; functions[static_cast(StorageClass::DB0_SET)] = unrefMember; @@ -866,4 +910,4 @@ namespace db0::object_model } } -} \ No newline at end of file +} diff --git a/src/dbzero/object_model/value/StorageClass.cpp b/src/dbzero/object_model/value/StorageClass.cpp index 65bd9d0a..fa041588 100644 --- a/src/dbzero/object_model/value/StorageClass.cpp +++ b/src/dbzero/object_model/value/StorageClass.cpp @@ -3,6 +3,7 @@ #include "StorageClass.hpp" #include +#include #include #include @@ -168,6 +169,7 @@ namespace std case StorageClass::PACK_2: return os << "PACK_2"; case StorageClass::OBJECT_WEAK_REF: return os << "OBJECT_WEAK_REF"; case StorageClass::OBJECT_LONG_WEAK_REF: return os << "OBJECT_LONG_WEAK_REF"; + case StorageClass::EMBEDDED_OBJECT_REF: return os << "EMBEDDED_OBJECT_REF"; case StorageClass::EMBEDDED_STRING: return os << "EMBEDDED_STRING"; case StorageClass::EMBEDDED_BYTES: return os << "EMBEDDED_BYTES"; case StorageClass::EMBEDDED_TUPLE: return os << "EMBEDDED_TUPLE"; @@ -193,8 +195,18 @@ namespace db0 db0::swine_ptr &fixture, ObjectPtr lang_value) { assert(pre_storage_class == PreStorageClass::OBJECT_WEAK_REF || pre_storage_class == PreStorageClass::OBJECT_REF); - const auto &obj = LangToolkit::getTypeManager().extractAnyObject(lang_value); - if (*obj.getFixture() != *fixture.get()) { + if (pre_storage_class == PreStorageClass::OBJECT_REF && db0::python::PyEmbeddedMemo_Check(lang_value)) { + auto embeddedFixture = db0::python::getEmbeddedMemoFixture(lang_value); + if (*embeddedFixture != *fixture.get()) { + THROWF(db0::InputException) + << "Embedded immutable object references cannot cross prefixes"; + } + return StorageClass::EMBEDDED_OBJECT_REF; + } + const auto &typeManager = LangToolkit::getTypeManager(); + const auto objFixture = typeManager.extractObjectFixture(lang_value); + if (*objFixture != *fixture.get()) { + typeManager.validateForeignObjectReference(lang_value); // must use long weak-ref instead, since referenced object is from a foreign prefix return StorageClass::OBJECT_LONG_WEAK_REF; } @@ -206,8 +218,18 @@ namespace db0 { assert(pre_storage_class == PreStorageClass::OBJECT_WEAK_REF || pre_storage_class == PreStorageClass::OBJECT_REF); - const auto &obj = LangToolkit::getTypeManager().extractAnyObject(lang_value); - if (*obj.getFixture() != fixture) { + if (pre_storage_class == PreStorageClass::OBJECT_REF && db0::python::PyEmbeddedMemo_Check(lang_value)) { + auto embeddedFixture = db0::python::getEmbeddedMemoFixture(lang_value); + if (*embeddedFixture != fixture) { + THROWF(db0::InputException) + << "Embedded immutable object references cannot cross prefixes"; + } + return StorageClass::EMBEDDED_OBJECT_REF; + } + const auto &typeManager = LangToolkit::getTypeManager(); + const auto objFixture = typeManager.extractObjectFixture(lang_value); + if (*objFixture != fixture) { + typeManager.validateForeignObjectReference(lang_value); // must use long weak-ref instead, since referenced object is from a foreign prefix return StorageClass::OBJECT_LONG_WEAK_REF; } diff --git a/src/dbzero/object_model/value/StorageClass.hpp b/src/dbzero/object_model/value/StorageClass.hpp index 010e7e93..d23ea0a8 100644 --- a/src/dbzero/object_model/value/StorageClass.hpp +++ b/src/dbzero/object_model/value/StorageClass.hpp @@ -118,6 +118,7 @@ namespace db0::object_model DB0_WEAK_SET = static_cast(PreStorageClass::DB0_WEAK_SET), // Embedded variable-length payloads stored inside another immutable object. // Count down from the top range so future PreStorageClass values can grow upward without collision. + EMBEDDED_OBJECT_REF = std::numeric_limits::max() - 9, EMBEDDED_STRING = std::numeric_limits::max() - 8, EMBEDDED_BYTES = std::numeric_limits::max() - 7, EMBEDDED_TUPLE = std::numeric_limits::max() - 6, @@ -182,6 +183,9 @@ namespace db0 if (storage_class == StorageClass::OBJECT_LONG_WEAK_REF) { return PreStorageClass::OBJECT_WEAK_REF; } + if (storage_class == StorageClass::EMBEDDED_OBJECT_REF) { + return PreStorageClass::OBJECT_REF; + } return static_cast(static_cast(storage_class)); } diff --git a/tests/unit_tests/EmbeddedObjectTest.cpp b/tests/unit_tests/EmbeddedObjectTest.cpp index acdd7aed..bb9a7a3d 100644 --- a/tests/unit_tests/EmbeddedObjectTest.cpp +++ b/tests/unit_tests/EmbeddedObjectTest.cpp @@ -155,9 +155,9 @@ namespace tests ASSERT_NE(variableValue, nullptr); ASSERT_EQ(variableValue->itemKind(), StorageClass::EMBEDDED_STRING); ASSERT_EQ(variableValue->stringPayload().toString(), "root variable string"); - ASSERT_EQ(&object->embeddedObject().pos_vt(), &object->pos_vt()); - ASSERT_EQ(&object->embeddedObject().index_vt(), &object->index_vt()); - ASSERT_EQ(&object->embeddedObject().field_map(), &object->field_map()); + ASSERT_EQ(&object->getObject().pos_vt(), &object->pos_vt()); + ASSERT_EQ(&object->getObject().index_vt(), &object->index_vt()); + ASSERT_EQ(&object->getObject().field_map(), &object->field_map()); } TEST_F( EmbeddedObjectTest , testEmbeddedObjectStoresVariableFieldsInDictMap ) diff --git a/tests/unit_tests/ObjectInitializerTest.cpp b/tests/unit_tests/ObjectInitializerTest.cpp index 0392f24e..b29baa49 100644 --- a/tests/unit_tests/ObjectInitializerTest.cpp +++ b/tests/unit_tests/ObjectInitializerTest.cpp @@ -882,7 +882,7 @@ namespace tests ASSERT_NE(embeddedValue, nullptr); ASSERT_EQ(embeddedValue->itemKind(), StorageClass::EMBEDDED_OBJECT); auto offset = offsetOfEmbeddedObject(*root.operator->(), *embeddedValue); - ASSERT_TRUE(root->embeddedObjectOffsets().contains(offset)); + ASSERT_TRUE(root->getOffsetIndex().contains(offset)); auto embedded = root.getEmbeddedInstanceAtOffset(offset); ASSERT_TRUE(embedded.get()); @@ -975,7 +975,7 @@ namespace tests auto *innerValue = outerObject.variableValue(outerLoc.first); ASSERT_NE(innerValue, nullptr); auto innerOffset = offsetOfEmbeddedObject(*root.operator->(), *innerValue); - ASSERT_TRUE(root->embeddedObjectOffsets().contains(innerOffset)); + ASSERT_TRUE(root->getOffsetIndex().contains(innerOffset)); auto embedded = root.getEmbeddedInstanceAtOffset(innerOffset); ASSERT_TRUE(embedded.get());