From c1f534db5d6bc29f9be0e7498e4971c7132d013a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 28 Aug 2013 07:52:50 +0200 Subject: Support for added and deleted data member pragmas --- odb/context.cxx | 18 +++++ odb/context.hxx | 54 +++++++++++++-- odb/pragma.cxx | 31 +++++++-- odb/relational/model.hxx | 9 +++ odb/relational/source.cxx | 102 ++++++++++++++++------------ odb/relational/source.hxx | 23 +++++++ odb/validator.cxx | 170 +++++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 343 insertions(+), 64 deletions(-) (limited to 'odb') diff --git a/odb/context.cxx b/odb/context.cxx index a4609df..35433c9 100644 --- a/odb/context.cxx +++ b/odb/context.cxx @@ -2471,6 +2471,12 @@ namespace if (m.count ("polymorphic-ref")) return; + // Ignore added/deleted members if so requested. + // + if (((flags_ & exclude_added) != 0 && added (member_path_)) || + ((flags_ & exclude_deleted) != 0 && deleted (member_path_))) + return; + if (context::is_a (member_path_, member_scope_, flags_)) r_++; @@ -2480,6 +2486,12 @@ namespace virtual void traverse_simple (semantics::data_member&) { + // Ignore added/deleted members if so requested. + // + if (((flags_ & exclude_added) != 0 && added (member_path_)) || + ((flags_ & exclude_deleted) != 0 && deleted (member_path_))) + return; + if (context::is_a (member_path_, member_scope_, flags_)) r_++; } @@ -2487,6 +2499,12 @@ namespace virtual void traverse_container (semantics::data_member&, semantics::type& c) { + // Ignore added/deleted members if so requested. + // + if (((flags_ & exclude_added) != 0 && added (member_path_)) || + ((flags_ & exclude_deleted) != 0 && deleted (member_path_))) + return; + // We don't cross the container boundaries (separate table). // unsigned short f (flags_ & (context::test_container | diff --git a/odb/context.hxx b/odb/context.hxx index 11a6713..be612f7 100644 --- a/odb/context.hxx +++ b/odb/context.hxx @@ -726,7 +726,7 @@ public: return m.count ("transient"); } - // Return the deletion version or 0 if not deleted. + // Return the deletion version or 0 if not fort-deleted. // static unsigned long long deleted (semantics::class_& c) @@ -735,9 +735,41 @@ public: } static unsigned long long - deleted (semantics::data_member& m) + deleted (data_member_path const& mp) { - return m.get ("deleted", 0); + unsigned long long r (0); + + // Find the earliest version since this member was deleted. + // + for (data_member_path::const_reverse_iterator i (mp.rbegin ()); + i != mp.rend (); ++i) + { + unsigned long long v ((*i)->get ("deleted", 0)); + if (v != 0 && v < r) + r = v; + } + + return r; + } + + // Return the addition version or 0 if not soft-added. + // + static unsigned long long + added (data_member_path const& mp) + { + unsigned long long r (0); + + // Find the latest version since this member was added. + // + for (data_member_path::const_reverse_iterator i (mp.rbegin ()); + i != mp.rend (); ++i) + { + unsigned long long v ((*i)->get ("added", 0)); + if (v != 0 && v > r) + r = v; + } + + return r; } static bool @@ -868,9 +900,12 @@ public: static object_section& section (data_member_path const& mp) { - // The direct member of the object specifies the section. + // The direct member of the object specifies the section. If the + // path is empty (which can happen, for example, for a container + // element), assume it is the main section. + // // - return section (*mp.front ()); + return mp.empty () ? main_section : section (*mp.front ()); } // Member belongs to a section that is loaded separately. @@ -1250,7 +1285,12 @@ public: // Treat eager loaded members as belonging to the main section. // If this flag is specified, then section must be main_section. // - static unsigned short const include_eager_load = 0x2000; + static unsigned short const include_eager_load = 0x800; + + // Exclude added/deleted members. + // + static unsigned short const exclude_added = 0x1000; + static unsigned short const exclude_deleted = 0x2000; // By default the test goes into bases for non-polymorphic // hierarchies and doesn't go for polymorphic. The following @@ -1274,7 +1314,7 @@ public: semantics::type&, string const& key_prefix); - // Return the number of matching entities. Can be uses as a just + // Return the number of matching entities. Can be used as a just // a bool value (0 means no match). // size_t diff --git a/odb/pragma.cxx b/odb/pragma.cxx index 8e55530..0c8881f 100644 --- a/odb/pragma.cxx +++ b/odb/pragma.cxx @@ -415,9 +415,21 @@ check_spec_decl_type (declaration const& d, return false; } } + else if (p == "added") + { + // Added can be used for data members only. + // + if (tc != FIELD_DECL) + { + error (l) << "name '" << name << "' in db pragma " << p << " does " + << "not refer to a data member" << endl; + return false; + } + } else if (p == "deleted") { - // Deleted can be used for both data members and classes (object). + // Deleted can be used for both data members and classes (object, + // view of composite value). // if (tc != FIELD_DECL && tc != RECORD_TYPE) { @@ -2290,8 +2302,9 @@ handle_pragma (cxx_lexer& l, tt = l.next (tl, &tn); } - else if (p == "deleted") + else if (p == "added" || p == "deleted") { + // added (unsigned long long version) // deleted (unsigned long long version) // @@ -2300,6 +2313,8 @@ handle_pragma (cxx_lexer& l, if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) return; + char const* n (p == "added" ? "addition" : "deletion"); + if (l.next (tl, &tn) != CPP_OPEN_PAREN) { error (l) << "'(' expected after db pragma " << p << endl; @@ -2308,7 +2323,7 @@ handle_pragma (cxx_lexer& l, if (l.next (tl, &tn) != CPP_NUMBER || TREE_CODE (tn) != INTEGER_CST) { - error (l) << "unsigned integer expected as deletion version" << endl; + error (l) << "unsigned integer expected as " << n << " version" << endl; return; } @@ -2316,7 +2331,7 @@ handle_pragma (cxx_lexer& l, if (v == 0) { - error (l) << "deletion version cannot be zero" << endl; + error (l) << n << " version cannot be zero" << endl; return; } @@ -3243,6 +3258,7 @@ handle_pragma_qualifier (cxx_lexer& l, string p) p == "unordered" || p == "readonly" || p == "transient" || + p == "added" || p == "deleted" || p == "version" || p == "virtual") @@ -3627,6 +3643,12 @@ handle_pragma_db_transient (cpp_reader* r) } extern "C" void +handle_pragma_db_added (cpp_reader* r) +{ + handle_pragma_qualifier (r, "added"); +} + +extern "C" void handle_pragma_db_deleted (cpp_reader* r) { handle_pragma_qualifier (r, "deleted"); @@ -3719,6 +3741,7 @@ register_odb_pragmas (void*, void*) c_register_pragma_with_expansion ("db", "unordered", handle_pragma_db_unordered); c_register_pragma_with_expansion ("db", "readonly", handle_pragma_db_readonly); c_register_pragma_with_expansion ("db", "transient", handle_pragma_db_transient); + c_register_pragma_with_expansion ("db", "added", handle_pragma_db_added); c_register_pragma_with_expansion ("db", "deleted", handle_pragma_db_deleted); c_register_pragma_with_expansion ("db", "version", handle_pragma_db_version); c_register_pragma_with_expansion ("db", "virtual", handle_pragma_db_virtual); diff --git a/odb/relational/model.hxx b/odb/relational/model.hxx index d4e1792..e345f83 100644 --- a/odb/relational/model.hxx +++ b/odb/relational/model.hxx @@ -102,6 +102,9 @@ namespace relational virtual bool traverse_column (semantics::data_member& m, string const& name, bool) { + if (deleted (member_path_)) + return false; + string col_id (id_prefix_ + (key_prefix_.empty () ? m.name () : key_prefix_)); @@ -230,6 +233,9 @@ namespace relational using sema_rel::column; using sema_rel::foreign_key; + if (deleted (member_path_)) + return; + // Ignore inverse object pointers. // if (inverse (m, key_prefix_)) @@ -488,6 +494,9 @@ namespace relational using sema_rel::column; + if (deleted (member_path_)) + return; + // Ignore inverse containers of object pointers. // if (inverse (m, "value")) diff --git a/odb/relational/source.cxx b/odb/relational/source.cxx index ba5a464..df804d2 100644 --- a/odb/relational/source.cxx +++ b/odb/relational/source.cxx @@ -49,6 +49,12 @@ traverse_object (type& c) (opt ? context::grow (*opt) : false); } + // Schema name as a string literal or empty. + // + string schema_name (options.schema_name ()[db]); + if (!schema_name.empty ()) + schema_name = strlit (schema_name); + string const& type (class_fq_name (c)); string traits ("access::object_traits_impl< " + type + ", id_" + db.string () + " >"); @@ -59,31 +65,6 @@ traverse_object (type& c) ? &poly_base->get ("user-sections") : 0); - // See what kind of containers we've got. - // - bool containers (has_a (c, test_container)); - bool straight_containers (false); - bool smart_containers (false); - - // Eager load and update containers. - // - bool load_containers (false); - bool update_containers (false); - - if (containers) - { - load_containers = has_a ( - c, test_container | include_eager_load, &main_section); - - if ((straight_containers = has_a (c, test_straight_container))) - { - // Only straight containers can be readwrite or smart. - // - smart_containers = has_a (c, test_smart_container); - update_containers = has_a (c, test_readwrite_container, &main_section); - } - } - os << "// " << class_name (c) << endl << "//" << endl << endl; @@ -103,6 +84,9 @@ traverse_object (type& c) // if (!reuse_abst && id != 0) { + bool sections (false); + bool containers (has_a (c, test_container)); + os << "struct " << traits << "::extra_statement_cache_type" << "{"; @@ -112,7 +96,6 @@ traverse_object (type& c) if (containers) os << endl; - bool sections (false); instance sm; for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) { @@ -170,7 +153,6 @@ traverse_object (type& c) // Containers (abstract and concrete). // - if (containers) { instance t (c); t->traverse (c); @@ -1011,6 +993,8 @@ traverse_object (type& c) // persist () // + bool persist_containers (has_a (c, test_straight_container)); + os << "void " << traits << "::" << endl << "persist (database& db, " << (auto_id ? "" : "const ") << "object_type& obj"; @@ -1199,13 +1183,13 @@ traverse_object (type& c) // Initialize id_image and binding if we are a root of a polymorphic // hierarchy or if we have non-inverse containers. // - if (!poly_derived && (poly || straight_containers)) + if (!poly_derived && (poly || persist_containers)) { // If this is a polymorphic root without containers, then we only // need to do this if we are not a top-level call. If we are poly- // abstract, then top will always be false. // - if (poly && !straight_containers && !abst) + if (poly && !persist_containers && !abst) os << "if (!top)" << "{"; @@ -1226,11 +1210,11 @@ traverse_object (type& c) os << "sts.optimistic_id_image_binding ().version++;"; os << "}"; - if (poly && !straight_containers && !abst) + if (poly && !persist_containers && !abst) os << "}"; } - if (straight_containers) + if (persist_containers) { os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" << endl; @@ -1283,6 +1267,9 @@ traverse_object (type& c) // if (id != 0 && (!readonly || poly)) { + bool update_containers ( + has_a (c, test_readwrite_container, &main_section)); + // See if we have any sections that we might have to update. // bool sections (false); @@ -1877,6 +1864,11 @@ traverse_object (type& c) // erase (id) // + size_t erase_containers (has_a (c, test_straight_container)); + bool erase_versioned_containers ( + erase_containers > + has_a (c, test_straight_container | exclude_deleted | exclude_added)); + if (id != 0) { os << "void " << traits << "::" << endl @@ -1963,10 +1955,15 @@ traverse_object (type& c) // here since in case of a custom schema, it might not be // there). // - if (straight_containers) + if (erase_containers) { - os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" - << endl; + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"; + + if (erase_versioned_containers) + os << "const schema_version_migration& svm (" << + "db.schema_version_migration (" << schema_name << "));"; + + os << endl; instance t (container_calls::erase_id_call); t->traverse (c); @@ -2000,7 +1997,9 @@ traverse_object (type& c) // erase (object) // - if (id != 0 && (poly || opt != 0 || smart_containers)) + bool erase_smart_containers (has_a (c, test_smart_container)); + + if (id != 0 && (poly || opt != 0 || erase_smart_containers)) { os << "void " << traits << "::" << endl << "erase (database& db, const object_type& obj"; @@ -2038,7 +2037,7 @@ traverse_object (type& c) << "throw abstract_class ();" << endl; - if (opt != 0 || smart_containers) + if (opt != 0 || erase_smart_containers) { string rsts (poly_derived ? "rsts" : "sts"); @@ -2067,7 +2066,7 @@ traverse_object (type& c) << endl; } - if (!abst || straight_containers) + if (!abst || erase_containers) { if (!id_ma->synthesized) os << "// From " << location_string (id_ma->loc, true) << endl; @@ -2113,11 +2112,16 @@ traverse_object (type& c) // there). // - if (straight_containers) + if (erase_containers) { os << "extra_statement_cache_type& esc (" << - "sts.extra_statement_cache ());" - << endl; + "sts.extra_statement_cache ());"; + + if (erase_versioned_containers) + os << "const schema_version_migration& svm (" << + "db.schema_version_migration (" << schema_name << "));"; + + os << endl; instance t (container_calls::erase_obj_call); t->traverse (c); @@ -2168,7 +2172,7 @@ traverse_object (type& c) // containers since it is more efficient than the find_() method // below. // - if (poly_derived || (poly && straight_containers)) + if (poly_derived || (poly && erase_containers)) { // Only do the check in the top-level call. // @@ -2188,7 +2192,7 @@ traverse_object (type& c) << "}"; } } - else if (straight_containers) + else if (erase_containers) { // Things get complicated here: we don't want to trash the // containers and then find out that the versions don't match @@ -2228,11 +2232,16 @@ traverse_object (type& c) // here since in case of a custom schema, it might not be // there). // - if (straight_containers) + if (erase_containers) { os << "extra_statement_cache_type& esc (" << - "sts.extra_statement_cache ());" - << endl; + "sts.extra_statement_cache ());"; + + if (erase_versioned_containers) + os << "const schema_version_migration& svm (" << + "db.schema_version_migration (" << schema_name << "));"; + + os << endl; instance t (container_calls::erase_obj_call); t->traverse (c); @@ -3111,6 +3120,9 @@ traverse_object (type& c) // // Load containers, reset/reload sections. // + bool load_containers ( + has_a (c, test_container | include_eager_load, &main_section)); + if (poly_derived || load_containers || uss.count (user_sections::count_new | diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx index 9dbb4e9..3bf7c95 100644 --- a/odb/relational/source.hxx +++ b/odb/relational/source.hxx @@ -957,6 +957,9 @@ namespace relational if (var_override_.empty ()) { + // Ignore inverse, separately-loaded members in the main + // section (nothing to persist). + // if (section_ == 0 && separate_load (mi.m) && inverse (mi.m)) return false; @@ -3635,6 +3638,26 @@ namespace relational throw operation_failed (); } + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (member_path_)); + unsigned long long dv (deleted (member_path_)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 || dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" << endl; + } + if (call_ != erase_id_call && (call_ != erase_obj_call || smart)) { os << "{"; diff --git a/odb/validator.cxx b/odb/validator.cxx index 2b0b46e..86fcf55 100644 --- a/odb/validator.cxx +++ b/odb/validator.cxx @@ -57,6 +57,7 @@ namespace traverse (type& m) { semantics::class_& c (dynamic_cast (m.scope ())); + bool obj (object (c)); // If the class is marked transient, then mark each non-virtual // data member as transient. @@ -74,7 +75,6 @@ namespace return; } - count_++; semantics::names* hint; semantics::type& t (utype (m, hint)); @@ -115,7 +115,9 @@ namespace // Make sure a member of a section is an immediate member of an object. // The same for the section member itself. // - if (!object (c)) + bool section (t.fq_name () == "::odb::section"); + + if (!obj) { if (m.count ("section-member")) { @@ -125,7 +127,7 @@ namespace valid_ = false; } - if (t.fq_name () == "::odb::section") + if (section) { os << m.file () << ":" << m.line () << ":" << m.column () << ": " << "error: section data member can only be a direct member of a " << @@ -137,7 +139,7 @@ namespace // Make sure the load and update pragmas are only specified on // section members. // - if (t.fq_name () != "::odb::section") + if (!section) { if (m.count ("section-load")) { @@ -156,6 +158,130 @@ namespace } } + // Check that the addition version makes sense. + // + unsigned long long av (m.get ("added", 0)); + if (av != 0) + { + location_t l (m.get ("added-location")); + + if (id (m)) + { + error (l) << "object id cannod be soft-added" << endl; + valid_ = false; + } + + if (version (m)) + { + error (l) << "optimistic concurrency version cannod be " + "soft-added" << endl; + valid_ = false; + } + + if (section) + { + error (l) << "section cannod be soft-added" << endl; + valid_ = false; + } + + if (!versioned ()) + { + error (l) << "added data member in a non-versioned object " << + "model" << endl; + valid_ = false; + } + else + { + model_version const& mv (version ()); + + if (av > mv.current) + { + error (l) << "addition version is greater than the current " << + "model version" << endl; + valid_ = false; + } + else if (av <= mv.base) + { + error (l) << "addition version is less than or equal to the " << + "base model version" << endl; + info (l) << "delete this pragma since migration to version " << + av << " is no longer possible" << endl; + valid_ = false; + } + } + } + + // Check that the deletion version makes sense. + // + unsigned long long dv (m.get ("deleted", 0)); + if (dv != 0) + { + location_t l (m.get ("deleted-location")); + + if (id (m)) + { + error (l) << "object id cannod be soft-deleted" << endl; + valid_ = false; + } + + if (version (m)) + { + error (l) << "optimistic concurrency version cannod be " + "soft-deleted" << endl; + valid_ = false; + } + + if (section) + { + error (l) << "section cannod be soft-deleted" << endl; + valid_ = false; + } + + if (!versioned ()) + { + error (l) << "deleted data member in a non-versioned object " << + "model" << endl; + valid_ = false; + } + else + { + model_version const& mv (version ()); + + if (dv > mv.current) + { + error (l) << "deletion version is greater than the current " << + "model version" << endl; + valid_ = false; + } + else if (dv <= mv.base) + { + error (l) << "deletion version is less than or equal to the " << + "base model version" << endl; + info (c.location ()) << "delete this data member since " << + "migration to version " << dv << " is no longer possible" << + endl; + valid_ = false; + } + } + } + + // Make sure that addition and deletion versions are properly ordered. + // We can have both the [av, dv] (added then deleted) and [dv, av] + // (deleted then re-added) intervals. + // + if (av != 0 && dv != 0 && av == dv) + { + location_t al (m.get ("added-location")); + location_t dl (m.get ("deleted-location")); + + error (al) << "addition and deletion versions are the same" << endl; + info (dl) << "deletion version is specified here" << endl; + valid_ = false; + } + + if (dv == 0) + count_++; // Don't include deleted members in the count. + // Resolve null overrides. // override_null (m); @@ -326,15 +452,15 @@ namespace virtual void traverse_object (type& c) { - // Check the the deletion version makes sense. + // Check that the deletion version makes sense. // - if (unsigned long long v = deleted (c)) + if (unsigned long long v = c.get ("deleted", 0)) { location_t l (c.get ("deleted-location")); if (!versioned ()) { - error (l) << "deleted member in non-versioned object model" << endl; + error (l) << "deleted class in a non-versioned object model" << endl; valid_ = false; } else @@ -937,10 +1063,23 @@ namespace // Pass 2. // + struct data_member2: traversal::data_member, context + { + data_member2 (bool& valid): valid_ (valid) {} + + virtual void + traverse (type&) + { + // Enable the names() calls below if adding any tests here. + } + + bool& valid_; + }; + struct class2: traversal::class_, context { class2 (bool& valid) - : valid_ (valid), has_lt_operator_ (0) + : valid_ (valid), has_lt_operator_ (0), member_ (valid) { // Find the has_lt_operator function template. // @@ -978,6 +1117,8 @@ namespace if (has_lt_operator_ == 0) valid_ = false; + + *this >> names_ >> member_; } virtual void @@ -1117,20 +1258,33 @@ namespace valid_ = false; } } + + // Check members. + // + //names (c); } virtual void traverse_view (type&) { + // Check members. + // + //names (c); } virtual void traverse_composite (type&) { + // Check members. + // + //names (c); } bool& valid_; tree has_lt_operator_; + + data_member1 member_; + traversal::names names_; }; } -- cgit v1.1