diff --git a/dbzero/dbzero/dbzero.pyi b/dbzero/dbzero/dbzero.pyi index dc2df94c..e544d584 100644 --- a/dbzero/dbzero/dbzero.pyi +++ b/dbzero/dbzero/dbzero.pyi @@ -610,8 +610,9 @@ def rename_field(class_obj: type, from_name: str, to_name: str) -> None: def set_field_access(class_obj: type, account_id: Union[int, Sequence[int]], mode: Tuple[EnumValue, ...], *fields: str) -> None: """Set protected-field access flags for one or more fields of a memo class. - The memo class must be declared with ``protect_fields=True``. Pass an empty - ``mode`` tuple to clear all access flags for the specified account and fields. + The memo class must be declared with ``protect_fields=True`` or inherit it + from a protected memo base. Pass an empty ``mode`` tuple to clear all access + flags for the specified account and fields. """ ... @@ -622,8 +623,9 @@ def get_field_access(class_obj: type, account_id: int) -> Iterable[Tuple[str, Tu def reset_protect_fields(class_obj: type) -> None: """Clear the persisted protect_fields flag for a memo class. - The memo type must no longer be decorated with ``protect_fields=True``. - Remove the argument or set it to ``False`` before calling this function. + The memo type must no longer be decorated with ``protect_fields=True`` and + must not inherit protected fields from a protected memo base. Remove the + argument or set it to ``False`` before calling this function. """ ... diff --git a/dbzero/dbzero/memo.py b/dbzero/dbzero/memo.py index 0f9f36d3..3c123963 100644 --- a/dbzero/dbzero/memo.py +++ b/dbzero/dbzero/memo.py @@ -172,6 +172,7 @@ def memo(cls: Optional[type] = None, **kwargs) -> type: If True, the persistent class is marked for field protection. Once the class is materialized, removing this argument from the Python definition does not clear the persisted flag; use reset_protect_fields on the dbzero Class object instead. + Derived memo classes inherit field protection and cannot disable it. Returns ------- diff --git a/python_tests/test_memo_protect_fields.py b/python_tests/test_memo_protect_fields.py index 74611f76..cc847d64 100644 --- a/python_tests/test_memo_protect_fields.py +++ b/python_tests/test_memo_protect_fields.py @@ -81,6 +81,25 @@ def __init__(self, count): setattr(self, f"init_field_{i}", values[i % len(values)](i)) +@db0.memo +@dataclass +class MemoUnprotectedBaseFieldsClass: + base_value: str + + +@db0.memo(protect_fields=True) +@dataclass +class MemoProtectedDerivedFieldsClass(MemoUnprotectedBaseFieldsClass): + name: str + value: int + + +@db0.memo +@dataclass +class MemoImplicitlyProtectedDerivedFieldsClass(MemoProtectedDerivedFieldsClass): + derived_value: float + + def get_memo_class_object(obj): return db0.get_memo_class(obj).get_class() @@ -242,6 +261,60 @@ def test_set_field_access_requires_protected_class(db0_fixture): assert False, "set_field_access should fail for unprotected classes" +def test_protect_fields_is_inherited_by_derived_memo_classes(db0_fixture): + obj = MemoImplicitlyProtectedDerivedFieldsClass("base", "alpha", 1, 1.5) + + assert get_memo_class_object(obj).get_type_flags()["protect_fields"] is True + db0.set_field_access(MemoImplicitlyProtectedDerivedFieldsClass, 123, (FieldAccess.READ,), "derived_value") + + +def test_protect_fields_cannot_be_disabled_in_derived_memo_class(db0_fixture): + with pytest.raises(RuntimeError, match="protect_fields.*base"): + @db0.memo(protect_fields=False) + @dataclass + class ExplicitlyUnprotectedDerived(MemoProtectedDerivedFieldsClass): + extra_value: str + + +def test_set_field_access_still_rejects_unprotected_base_of_protected_class(db0_fixture): + MemoImplicitlyProtectedDerivedFieldsClass("base", "alpha", 1, 1.5) + + with pytest.raises(RuntimeError, match="protected fields"): + db0.set_field_access(MemoUnprotectedBaseFieldsClass, 123, (FieldAccess.READ,), "base_value") + + +def test_reset_protect_fields_rejects_inherited_protection(db0_fixture): + obj = MemoImplicitlyProtectedDerivedFieldsClass("base", "alpha", 1, 1.5) + memo_class = get_memo_class_object(obj) + + with pytest.raises(RuntimeError, match="inherits.*protect_fields"): + db0.reset_protect_fields(MemoImplicitlyProtectedDerivedFieldsClass) + + assert memo_class.get_type_flags()["protect_fields"] is True + + +def test_class_reset_protect_fields_rejects_inherited_protection(db0_fixture): + obj = MemoImplicitlyProtectedDerivedFieldsClass("base", "alpha", 1, 1.5) + memo_class = get_memo_class_object(obj) + + with pytest.raises(RuntimeError, match="inherits.*protect_fields"): + memo_class.reset_protect_fields() + + assert memo_class.get_type_flags()["protect_fields"] is True + + +def test_explicit_true_is_allowed_on_class_derived_from_protected_base(db0_fixture): + @db0.memo(protect_fields=True) + @dataclass + class ExplicitlyProtectedDerived(MemoProtectedDerivedFieldsClass): + extra_value: str + + obj = ExplicitlyProtectedDerived("base", "alpha", 1, "extra") + + assert get_memo_class_object(obj).get_type_flags()["protect_fields"] is True + db0.set_field_access(ExplicitlyProtectedDerived, 123, (FieldAccess.READ,), "extra_value") + + def test_set_field_access_accepts_unknown_field_names(db0_fixture): MemoProtectedFieldsClass("alpha", 1) @@ -376,6 +449,156 @@ def test_protected_field_getter_requires_read_access(db0_fixture): _ = obj.value +def test_protected_field_getter_does_not_inherit_base_class_masks(db0_fixture): + account_id = ContextVar("protected_inheritance_account_id") + obj = MemoImplicitlyProtectedDerivedFieldsClass("base", "alpha", 1, 1.5) + db0.set_field_access(MemoProtectedDerivedFieldsClass, 123, (FieldAccess.READ,), "base_value", "name") + db0.set_field_access(MemoImplicitlyProtectedDerivedFieldsClass, 123, (FieldAccess.READ,), "derived_value") + db0._init_data_masking(account_id) + account_id.set(123) + + assert obj.derived_value == 1.5 + with pytest.raises(PermissionError, match="read"): + _ = obj.base_value + with pytest.raises(PermissionError, match="read"): + _ = obj.name + with pytest.raises(PermissionError, match="read"): + _ = obj.value + + +def test_protected_field_getter_uses_derived_class_own_masks(db0_fixture): + account_id = ContextVar("protected_inheritance_allow_override_account_id") + obj = MemoImplicitlyProtectedDerivedFieldsClass("base", "alpha", 1, 1.5) + db0.set_field_access(MemoProtectedDerivedFieldsClass, 123, (), "name") + db0.set_field_access(MemoImplicitlyProtectedDerivedFieldsClass, 123, (FieldAccess.READ,), "name") + db0._init_data_masking(account_id) + account_id.set(123) + + assert obj.name == "alpha" + with pytest.raises(PermissionError, match="read"): + _ = obj.value + + +def test_inherited_protect_fields_survives_reopen_without_parameter(db0_fixture): + @db0.memo(id="dbzero-software/dbzero/tests/protected-inherited-reopen-base") + @dataclass + class ReopenBase: + base_value: str + + @db0.memo(id="dbzero-software/dbzero/tests/protected-inherited-reopen-mid", protect_fields=True) + @dataclass + class ReopenMid(ReopenBase): + name: str + + @db0.memo(id="dbzero-software/dbzero/tests/protected-inherited-reopen-derived") + @dataclass + class ReopenDerived(ReopenMid): + value: int + + obj = ReopenDerived("base", "alpha", 1) + obj_id = db0.uuid(obj) + assert get_memo_class_object(obj).get_type_flags()["protect_fields"] is True + db0.commit() + + db0.close() + db0.init(DB0_DIR) + db0.open("my-test-prefix") + + @db0.memo(id="dbzero-software/dbzero/tests/protected-inherited-reopen-base") + @dataclass + class ReopenBaseAfter: + base_value: str + + @db0.memo(id="dbzero-software/dbzero/tests/protected-inherited-reopen-mid") + @dataclass + class ReopenMidAfter(ReopenBaseAfter): + name: str + + @db0.memo(id="dbzero-software/dbzero/tests/protected-inherited-reopen-derived") + @dataclass + class ReopenDerivedAfter(ReopenMidAfter): + value: int + + obj = db0.fetch(ReopenDerivedAfter, obj_id) + assert get_memo_class_object(obj).get_type_flags()["protect_fields"] is True + db0.set_field_access(ReopenDerivedAfter, 123, (FieldAccess.READ,), "value") + + +def test_derived_class_inherits_protect_fields_enabled_on_base_after_materialization(db0_fixture): + @db0.memo(id="dbzero-software/dbzero/tests/protected-base-enabled-later-base") + @dataclass + class BaseBefore: + base_value: str + + @db0.memo(id="dbzero-software/dbzero/tests/protected-base-enabled-later-derived") + @dataclass + class DerivedBefore(BaseBefore): + value: int + + obj = DerivedBefore("base", 1) + obj_id = db0.uuid(obj) + assert get_memo_class_object(obj).get_type_flags()["protect_fields"] is False + with pytest.raises(RuntimeError, match="protected fields"): + db0.set_field_access(DerivedBefore, 123, (FieldAccess.READ,), "value") + db0.commit() + + db0.close() + db0.init(DB0_DIR) + db0.open("my-test-prefix") + + @db0.memo(id="dbzero-software/dbzero/tests/protected-base-enabled-later-base", protect_fields=True) + @dataclass + class BaseAfter: + base_value: str + + @db0.memo(id="dbzero-software/dbzero/tests/protected-base-enabled-later-derived") + @dataclass + class DerivedAfter(BaseAfter): + value: int + + obj = db0.fetch(DerivedAfter, obj_id) + assert get_memo_class_object(obj).get_type_flags()["protect_fields"] is True + db0.set_field_access(DerivedAfter, 123, (FieldAccess.READ,), "value") + + +def test_derived_class_stops_inheriting_protect_fields_after_base_reset(db0_fixture): + @db0.memo(id="dbzero-software/dbzero/tests/protected-base-reset-base", protect_fields=True) + @dataclass + class BaseBefore: + base_value: str + + @db0.memo(id="dbzero-software/dbzero/tests/protected-base-reset-derived") + @dataclass + class DerivedBefore(BaseBefore): + value: int + + obj = DerivedBefore("base", 1) + obj_id = db0.uuid(obj) + assert get_memo_class_object(obj).get_type_flags()["protect_fields"] is True + db0.commit() + + db0.close() + db0.init(DB0_DIR) + db0.open("my-test-prefix") + + @db0.memo(id="dbzero-software/dbzero/tests/protected-base-reset-base", protect_fields=False) + @dataclass + class BaseAfter: + base_value: str + + @db0.memo(id="dbzero-software/dbzero/tests/protected-base-reset-derived") + @dataclass + class DerivedAfter(BaseAfter): + value: int + + obj = db0.fetch(DerivedAfter, obj_id) + db0.reset_protect_fields(BaseAfter) + + assert get_memo_class_object(obj).get_type_flags()["protect_fields"] is False + with pytest.raises(RuntimeError, match="protected fields"): + db0.set_field_access(DerivedAfter, 123, (FieldAccess.READ,), "value") + + def test_protected_field_getter_returns_missing_value_placeholder(db0_fixture): account_id = ContextVar("protected_placeholder_account_id") missing_value = object() diff --git a/src/dbzero/bindings/python/Memo.cpp b/src/dbzero/bindings/python/Memo.cpp index 1ee36ca5..3d573133 100644 --- a/src/dbzero/bindings/python/Memo.cpp +++ b/src/dbzero/bindings/python/Memo.cpp @@ -12,6 +12,7 @@ #include "Migration.hpp" #include "PyHash.hpp" #include "DataMasking.hpp" +#include #include #include #include @@ -902,7 +903,7 @@ namespace db0::python PyObject *wrapPyType(PyTypeObject *base_class, bool is_singleton, bool no_default_tags, const char *prefix_name, const char *type_id, const char *file_name, std::vector &&init_vars, PyObject *py_dyn_prefix_callable, - std::vector &&migrations, bool no_cache, bool immutable, bool protect_fields) + std::vector &&migrations, bool no_cache, bool immutable, std::optional protect_fields_option) { auto py_class = Py_BORROW(base_class); auto py_module = Py_OWN(findModule(*Py_OWN(PyObject_GetAttrString((PyObject*)*py_class, "__module__")))); @@ -936,6 +937,15 @@ namespace db0::python return nullptr; } + auto base_memo_type = PyToolkit::getBaseMemoType(*new_type); + bool inherited_protect_fields = base_memo_type + && MemoTypeDecoration::get(base_memo_type).getFlags()[MemoOptions::PROTECT_FIELDS]; + if (inherited_protect_fields && protect_fields_option == false) { + THROWF(db0::InputException) + << "Cannot set protect_fields=False on a class derived from a protect_fields base class"; + } + bool protect_fields = protect_fields_option.value_or(false); + MemoFlags type_flags = no_default_tags ? MemoFlags { MemoOptions::NO_DEFAULT_TAGS } : MemoFlags(); if (no_cache) { type_flags.set(MemoOptions::NO_CACHE); @@ -1000,7 +1010,10 @@ namespace db0::python bool no_default_tags = py_no_default_tags && PyObject_IsTrue(py_no_default_tags); bool no_cache = py_no_cache && PyObject_IsTrue(py_no_cache); bool immutable = py_immutable && PyObject_IsTrue(py_immutable); - bool protect_fields = py_protect_fields && PyObject_IsTrue(py_protect_fields); + std::optional protect_fields_option; + if (py_protect_fields) { + protect_fields_option = PyObject_IsTrue(py_protect_fields); + } const char *prefix_name = (py_prefix_name && py_prefix_name != Py_None) ? PyUnicode_AsUTF8(py_prefix_name) : nullptr; const char *type_id = py_type_id ? PyUnicode_AsUTF8(py_type_id) : nullptr; const char *file_name = (py_file_name && py_file_name != Py_None) ? PyUnicode_AsUTF8(py_file_name) : nullptr; @@ -1035,7 +1048,7 @@ namespace db0::python auto migrations = extractMigrations(py_migrations); return wrapPyType(castToType(class_obj), is_singleton, no_default_tags, prefix_name, type_id, file_name, - std::move(init_vars), py_dyn_prefix, std::move(migrations), no_cache, immutable, protect_fields + std::move(init_vars), py_dyn_prefix, std::move(migrations), no_cache, immutable, protect_fields_option ); } diff --git a/src/dbzero/bindings/python/PyAPI.cpp b/src/dbzero/bindings/python/PyAPI.cpp index 7fb86ebc..d2ee6281 100644 --- a/src/dbzero/bindings/python/PyAPI.cpp +++ b/src/dbzero/bindings/python/PyAPI.cpp @@ -944,7 +944,7 @@ namespace db0::python auto fixture_uuid = MemoTypeDecoration::get(reinterpret_cast(py_type)).getFixtureUUID(); auto fixture = PyToolkit::getPyWorkspace().getWorkspace().getFixture(fixture_uuid, AccessType::READ_WRITE); auto &class_factory = fixture->get(); - auto type = class_factory.getExistingType(reinterpret_cast(py_type)); + auto type = class_factory.getOrCreateType(reinterpret_cast(py_type)); if (!type->isProtectFields()) { THROWF(db0::InputException) << "Class " << type->getName() << " does not have protected fields enabled"; } @@ -979,7 +979,7 @@ namespace db0::python auto fixture_uuid = MemoTypeDecoration::get(reinterpret_cast(py_type)).getFixtureUUID(); auto fixture = PyToolkit::getPyWorkspace().getWorkspace().getFixture(fixture_uuid, AccessType::READ_WRITE); auto &class_factory = fixture->get(); - auto type = class_factory.getExistingType(reinterpret_cast(py_type)); + auto type = class_factory.getOrCreateType(reinterpret_cast(py_type)); if (!type->isProtectFields()) { THROWF(db0::InputException) << "Class " << type->getName() << " does not have protected fields enabled"; } @@ -1019,16 +1019,22 @@ namespace db0::python auto memo_type = reinterpret_cast(py_type); auto &decor = MemoTypeDecoration::get(memo_type); - if (decor.getFlags()[MemoOptions::PROTECT_FIELDS]) { - THROWF(db0::InputException) - << "Type is still decorated with protect_fields=True; remove it or set protect_fields=False first"; - } - using ClassFactory = db0::object_model::ClassFactory; auto fixture_uuid = decor.getFixtureUUID(AccessType::READ_WRITE); auto fixture = PyToolkit::getPyWorkspace().getWorkspace().getFixture(fixture_uuid, AccessType::READ_WRITE); auto &class_factory = fixture->get(); - auto type = class_factory.getExistingType(memo_type); + auto type = class_factory.getOrCreateType(memo_type); + + if (type->getBaseClassPtr() && type->getBaseClassPtr()->isProtectFields()) { + THROWF(db0::InputException) + << "Cannot disable protected fields on class " << type->getName() + << " because it inherits from a protect_fields base class"; + } + + if (decor.getFlags()[MemoOptions::PROTECT_FIELDS]) { + THROWF(db0::InputException) + << "Type is still decorated with protect_fields=True; remove it or set protect_fields=False first"; + } db0::FixtureLock lock(fixture); type->resetProtectFields(); diff --git a/src/dbzero/bindings/python/PyInternalAPI.cpp b/src/dbzero/bindings/python/PyInternalAPI.cpp index 3d21a1a1..9dd9558e 100644 --- a/src/dbzero/bindings/python/PyInternalAPI.cpp +++ b/src/dbzero/bindings/python/PyInternalAPI.cpp @@ -186,7 +186,7 @@ namespace db0::python // validate type if requested (no validation for MemoBase) if (py_expected_type && !PyToolkit::getTypeManager().isMemoBase(py_expected_type)) { // in other cases the type must match the actual object type - auto expected_class = class_factory.getExistingType(py_expected_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(), expected_class->getInstanceFlags() @@ -1100,4 +1100,4 @@ namespace db0::python template PyObject *getMaterializedMemoObject(MemoObject *); template PyObject *getMaterializedMemoObject(MemoImmutableObject *); -} \ No newline at end of file +} diff --git a/src/dbzero/object_model/class/Class.cpp b/src/dbzero/object_model/class/Class.cpp index 18c87b09..768f8e01 100644 --- a/src/dbzero/object_model/class/Class.cpp +++ b/src/dbzero/object_model/class/Class.cpp @@ -307,10 +307,30 @@ namespace db0::object_model return (*this)->m_flags[ClassOptions::IMMUTABLE]; } - bool Class::isProtectFields() const { + bool Class::hasOwnProtectFields() const { return (*this)->m_flags[ClassOptions::PROTECT_FIELDS]; } + bool Class::isProtectFields() const { + if (m_protect_fields_cache) { + return *m_protect_fields_cache; + } + + m_protect_fields_cache = hasOwnProtectFields() + || (m_base_class_ptr && m_base_class_ptr->isProtectFields()); + return *m_protect_fields_cache; + } + + void Class::resetProtectFieldsCache() const + { + m_protect_fields_cache.reset(); + getClassFactory(*getFixture()).forAll([this](const Class &type) { + if (type.isBaseClass(*this)) { + type.m_protect_fields_cache.reset(); + } + }); + } + void Class::assertFieldSafeSupported() const { if ((*this)->getObjVer() < FIELD_SAFE_MIN_VERSION) { @@ -343,10 +363,17 @@ namespace db0::object_model void Class::setProtectFields() { ensureFieldSafe(); modify().m_flags.set(ClassOptions::PROTECT_FIELDS, true); + resetProtectFieldsCache(); } void Class::resetProtectFields() { + if (m_base_class_ptr && m_base_class_ptr->isProtectFields()) { + THROWF(db0::InputException) + << "Cannot disable protected fields on class " << getName() + << " because it inherits from a protect_fields base class"; + } modify().m_flags.set(ClassOptions::PROTECT_FIELDS, false); + resetProtectFieldsCache(); } bool Class::hasFieldSafe() const @@ -383,7 +410,7 @@ namespace db0::object_model THROWF(db0::InputException) << "At least one field name is required"; } - auto &field_safe = getFieldSafe(); + auto &field_safe = ensureFieldSafe(); auto &field_id_mapper = field_safe.getFieldIDMapper(); auto &field_mask_manager = field_safe.getFieldMaskManager(); @@ -391,7 +418,7 @@ namespace db0::object_model field_offsets.reserve(field_names.size()); for (const auto &field_name: field_names) { auto member = tryGetMember(field_name.c_str()); - if (member) { + if (member && mask.value() != 0) { field_offsets.push_back(field_id_mapper.assignFieldOffset(member->m_field_id)); } else { field_offsets.push_back(field_id_mapper.assignFieldOffset(field_name.c_str())); @@ -412,18 +439,18 @@ namespace db0::object_model return {}; } - auto &field_safe = getFieldSafe(); - auto maybe_offset = field_safe.getFieldIDMapper().tryGetAssignedFieldOffset(member.m_field_id); - if (!maybe_offset) { + if (!hasFieldSafe()) { return {}; } + auto &field_safe = getFieldSafe(); auto field_mask = field_safe.getFieldMaskManager().tryGetFieldMask(account_id); - if (!field_mask) { - return {}; + auto maybe_offset = field_safe.getFieldIDMapper().tryGetAssignedFieldOffset(member.m_field_id); + if (maybe_offset && field_mask) { + return field_mask->getAssignedMask(*maybe_offset); } - return field_mask->getAssignedMask(*maybe_offset); + return {}; } std::optional Class::tryGetFieldAccess(std::uint64_t account_id, const MemberLoc &member_loc) const @@ -434,7 +461,10 @@ namespace db0::object_model } auto member = tryGetMember(member_id.primary().first); - return member ? tryGetFieldAccess(account_id, *member) : std::nullopt; + if (member) { + return tryGetFieldAccess(account_id, *member); + } + return {}; } std::vector > Class::getFieldAccess(std::uint64_t account_id) const diff --git a/src/dbzero/object_model/class/Class.hpp b/src/dbzero/object_model/class/Class.hpp index 04a09729..99d0033c 100644 --- a/src/dbzero/object_model/class/Class.hpp +++ b/src/dbzero/object_model/class/Class.hpp @@ -176,6 +176,7 @@ DB0_PACKED_END bool isImmutable() const; bool assignDefaultTags() const; bool isProtectFields() const; + bool hasOwnProtectFields() const; void setProtectFields(); void resetProtectFields(); bool hasFieldSafe() const; @@ -332,6 +333,7 @@ DB0_PACKED_END Schema m_schema; mutable std::optional m_field_safe; std::shared_ptr m_base_class_ptr; + mutable std::optional m_protect_fields_cache; // Field by-name index (cache) // values: member ID / assigned on initialization flag @@ -352,6 +354,7 @@ DB0_PACKED_END // callback for MemberID updates void onMemberIDUpdated(const MemberID &) const; void assertFieldSafeSupported() const; + void resetProtectFieldsCache() const; FieldSafe &ensureFieldSafe(); void openFieldSafe() const; // translate member's field ID into a unique key diff --git a/src/dbzero/object_model/class/ClassFactory.cpp b/src/dbzero/object_model/class/ClassFactory.cpp index e5c27c57..752b9eb9 100644 --- a/src/dbzero/object_model/class/ClassFactory.cpp +++ b/src/dbzero/object_model/class/ClassFactory.cpp @@ -136,7 +136,7 @@ namespace db0::object_model } return type; } - + std::shared_ptr ClassFactory::tryGetOrCreateType(TypeObjectPtr lang_type) { // disallow creating MemoBase type @@ -155,7 +155,11 @@ namespace db0::object_model if (class_ptr) { // pull existing dbzero class instance by pointer type = getTypeByPtr(class_ptr, lang_type).m_class; - if (LangToolkit::isProtectFields(lang_type) && !type->isProtectFields()) { + auto memo_base = LangToolkit::getBaseMemoType(lang_type); + if (memo_base) { + getOrCreateType(memo_base); + } + if (LangToolkit::isProtectFields(lang_type) && !type->hasOwnProtectFields()) { type->setProtectFields(); } } else { @@ -172,12 +176,12 @@ namespace db0::object_model } flags.set(ClassOptions::NO_DEFAULT_TAGS, LangToolkit::isNoDefaultTags(lang_type)); flags.set(ClassOptions::IMMUTABLE, LangToolkit::isImmutable(lang_type)); - flags.set(ClassOptions::PROTECT_FIELDS, LangToolkit::isProtectFields(lang_type)); auto memo_base = LangToolkit::getBaseMemoType(lang_type); std::shared_ptr base_class; if (memo_base) { base_class = getOrCreateType(memo_base); } + flags.set(ClassOptions::PROTECT_FIELDS, LangToolkit::isProtectFields(lang_type)); type = std::shared_ptr(new Class(fixture, LangToolkit::getTypeName(lang_type), LangToolkit::tryGetModuleName(lang_type), type_id, prefix_name, init_vars, flags, base_class) ); @@ -202,8 +206,14 @@ namespace db0::object_model it_cached = m_type_cache.insert({lang_type, type}).first; m_pending_types.push_back(lang_type); - } else if (LangToolkit::isProtectFields(lang_type) && !it_cached->second->isProtectFields()) { - it_cached->second->setProtectFields(); + } else { + auto memo_base = LangToolkit::getBaseMemoType(lang_type); + if (memo_base) { + getOrCreateType(memo_base); + } + if (LangToolkit::isProtectFields(lang_type) && !it_cached->second->hasOwnProtectFields()) { + it_cached->second->setProtectFields(); + } } return it_cached->second; } @@ -240,7 +250,7 @@ namespace db0::object_model it_cached->second.m_class->setInitVars(LangToolkit::getInitVars(lang_type)); it_cached->second.m_class->setRuntimeFlags(LangToolkit::getMemoFlags(lang_type)); } - if (lang_type && LangToolkit::isProtectFields(lang_type) && !it_cached->second.m_class->isProtectFields()) { + if (lang_type && LangToolkit::isProtectFields(lang_type) && !it_cached->second.m_class->hasOwnProtectFields()) { it_cached->second.m_class->setProtectFields(); } return it_cached->second.m_class; @@ -307,7 +317,7 @@ namespace db0::object_model if (lang_type) { type->setInitVars(LangToolkit::getInitVars(lang_type)); type->setRuntimeFlags(LangToolkit::getMemoFlags(lang_type)); - if (LangToolkit::isProtectFields(lang_type) && !type->isProtectFields()) { + if (LangToolkit::isProtectFields(lang_type) && !type->hasOwnProtectFields()) { type->setProtectFields(); } } @@ -321,7 +331,7 @@ namespace db0::object_model it_cached->second.m_class->setInitVars(LangToolkit::getInitVars(lang_type)); it_cached->second.m_class->setRuntimeFlags(LangToolkit::getMemoFlags(lang_type)); } - if (lang_type && LangToolkit::isProtectFields(lang_type) && !it_cached->second.m_class->isProtectFields()) { + if (lang_type && LangToolkit::isProtectFields(lang_type) && !it_cached->second.m_class->hasOwnProtectFields()) { it_cached->second.m_class->setProtectFields(); } return it_cached->second; diff --git a/src/dbzero/object_model/class/ClassFactory.hpp b/src/dbzero/object_model/class/ClassFactory.hpp index 2fb99eb8..20b7a2d6 100644 --- a/src/dbzero/object_model/class/ClassFactory.hpp +++ b/src/dbzero/object_model/class/ClassFactory.hpp @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -155,4 +156,4 @@ DB0_PACKED_END std::optional getNameVariant(ClassFactory::TypeObjectPtr lang_type, const char *type_id, int variant_id); -} \ No newline at end of file +} diff --git a/src/dbzero/object_model/class/FieldIDMapper.cpp b/src/dbzero/object_model/class/FieldIDMapper.cpp index 3e15489b..114a3a9d 100644 --- a/src/dbzero/object_model/class/FieldIDMapper.cpp +++ b/src/dbzero/object_model/class/FieldIDMapper.cpp @@ -280,6 +280,12 @@ namespace db0::object_model return std::nullopt; } + std::optional FieldIDMapper::tryGetAssignedFieldOffset(const char *field_name) const + { + assert(field_name); + return tryGetNameOffset(field_name); + } + std::uint32_t FieldIDMapper::assignFieldOffset(FieldID field_id) { auto maybe_offset = tryGetAssignedFieldOffset(field_id); diff --git a/src/dbzero/object_model/class/FieldIDMapper.hpp b/src/dbzero/object_model/class/FieldIDMapper.hpp index bb1a07ed..aa50f19e 100644 --- a/src/dbzero/object_model/class/FieldIDMapper.hpp +++ b/src/dbzero/object_model/class/FieldIDMapper.hpp @@ -58,6 +58,7 @@ DB0_PACKED_END bool renameField(const char *old_field_name, const char *new_field_name); std::optional tryGetAssignedFieldOffset(FieldID) const; + std::optional tryGetAssignedFieldOffset(const char *field_name) const; std::unordered_map getAssignedNameOffsets() const; std::uint32_t getFieldOffsetRange() const;