From 09d7377f81aeb8fde4aa1698e946457f03380d45 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 6 May 2013 12:05:39 +0200 Subject: Add support for object sections Sections are an optimization mechanism that allows the partitioning of data members of a persistent class into groups that can be separately loaded and/or updated. --- odb/common.cxx | 48 +- odb/common.hxx | 38 +- odb/context.cxx | 247 +++++- odb/context.hxx | 305 +++++++- odb/context.ixx | 27 + odb/features.hxx | 1 + odb/header.cxx | 44 +- odb/inline.cxx | 26 + odb/pragma.cxx | 157 +++- odb/processor.cxx | 372 ++++++++- odb/relational/common.hxx | 13 +- odb/relational/context.cxx | 2 +- odb/relational/context.hxx | 8 +- odb/relational/context.ixx | 4 +- odb/relational/header.cxx | 84 +- odb/relational/header.hxx | 198 +++++ odb/relational/inline.hxx | 89 ++- odb/relational/mssql/header.cxx | 26 + odb/relational/mssql/source.cxx | 51 +- odb/relational/mysql/context.cxx | 32 +- odb/relational/mysql/context.hxx | 2 +- odb/relational/oracle/source.cxx | 12 + odb/relational/pgsql/context.cxx | 46 +- odb/relational/pgsql/context.hxx | 2 +- odb/relational/pgsql/header.cxx | 48 +- odb/relational/pgsql/source.cxx | 163 +++- odb/relational/source.cxx | 1093 ++++++++++++++++++++++---- odb/relational/source.hxx | 1540 ++++++++++++++++++++++++++++++++----- odb/relational/sqlite/context.cxx | 32 +- odb/relational/sqlite/context.hxx | 2 +- odb/validator.cxx | 156 +++- 31 files changed, 4328 insertions(+), 540 deletions(-) create mode 100644 odb/context.ixx (limited to 'odb') diff --git a/odb/common.cxx b/odb/common.cxx index 01df033..365fe19 100644 --- a/odb/common.cxx +++ b/odb/common.cxx @@ -193,6 +193,14 @@ traverse_member (semantics::data_member& m, semantics::type& t) traverse_simple (m); } +bool object_members_base:: +section_test (data_member_path const& mp) +{ + // By default ignore members from the wrong section. + // + return section_ == 0 || *section_ == section (mp); +} + void object_members_base::member:: traverse (semantics::data_member& m) { @@ -201,14 +209,19 @@ traverse (semantics::data_member& m) om_.member_path_.push_back (&m); - semantics::type& t (utype (m)); + // Ignore members from the wrong section. + // + if (om_.section_test (om_.member_path_)) + { + semantics::type& t (utype (m)); - if (semantics::type* c = context::container (m)) - om_.traverse_container (m, *c); - else if (semantics::class_* c = object_pointer (t)) - om_.traverse_pointer (m, *c); - else - om_.traverse_member (m, t); + if (semantics::type* c = context::container (m)) + om_.traverse_container (m, *c); + else if (semantics::class_* c = object_pointer (t)) + om_.traverse_pointer (m, *c); + else + om_.traverse_member (m, t); + } om_.member_path_.pop_back (); } @@ -393,6 +406,14 @@ traverse_member (semantics::data_member& m, semantics::type& t) } } +bool object_columns_base:: +section_test (data_member_path const& mp) +{ + // By default ignore members from the wrong section. + // + return section_ == 0 || *section_ == section (mp); +} + void object_columns_base::member:: traverse (semantics::data_member& m) { @@ -406,12 +427,15 @@ traverse (semantics::data_member& m) oc_.member_path_.push_back (&m); - semantics::type& t (utype (m)); + if (oc_.section_test (oc_.member_path_)) + { + semantics::type& t (utype (m)); - if (semantics::class_* c = object_pointer (t)) - oc_.traverse_pointer (m, *c); - else - oc_.traverse_member (m, t); + if (semantics::class_* c = object_pointer (t)) + oc_.traverse_pointer (m, *c); + else + oc_.traverse_member (m, t); + } oc_.member_path_.pop_back (); } diff --git a/odb/common.hxx b/odb/common.hxx index 5ca5107..c4ecbaf 100644 --- a/odb/common.hxx +++ b/odb/common.hxx @@ -67,9 +67,13 @@ struct object_members_base: traversal::class_, virtual context virtual void traverse_view (semantics::class_&); + virtual bool + section_test (data_member_path const&); + public: - object_members_base (bool traverse_poly_base = false) - : top_level_ (true), member_ (*this) + object_members_base (bool traverse_poly_base = false, + object_section* section = 0) + : section_ (section), top_level_ (true), member_ (*this) { init (false, false, false, traverse_poly_base); } @@ -77,8 +81,9 @@ public: object_members_base (bool build_flat_prefix, bool build_table_prefix, bool build_member_prefix, - bool traverse_poly_base = false) - : top_level_ (true), member_ (*this) + bool traverse_poly_base = false, + object_section* section = 0) + : section_ (section), top_level_ (true), member_ (*this) { init (build_flat_prefix, build_table_prefix, @@ -88,6 +93,7 @@ public: object_members_base (object_members_base const& x) : context (), //@@ -Wextra + section_ (x.section_), top_level_ (true), member_ (*this) { @@ -108,6 +114,8 @@ protected: data_member_path member_path_; data_member_scope member_scope_; + object_section* section_; + protected: semantics::data_member* id () const @@ -138,10 +146,7 @@ private: struct member: traversal::data_member { - member (object_members_base& om) - : om_ (om) - { - } + member (object_members_base& om): om_ (om) {} virtual void traverse (semantics::data_member&); @@ -211,10 +216,15 @@ struct object_columns_base: traversal::class_, virtual context virtual void flush (); + virtual bool + section_test (data_member_path const&); + public: object_columns_base (bool first = true, - column_prefix const& cp = column_prefix ()) + column_prefix const& cp = column_prefix (), + object_section* section = 0) : column_prefix_ (cp), + section_ (section), root_ (0), traverse_poly_base_ (false), first_ (first), @@ -224,8 +234,11 @@ public: init (); } - object_columns_base (bool first, bool traverse_poly_base) - : root_ (0), + object_columns_base (bool first, + bool traverse_poly_base, + object_section* section = 0) + : section_ (section), + root_ (0), traverse_poly_base_ (traverse_poly_base), first_ (first), top_level_ (true), @@ -237,6 +250,7 @@ public: object_columns_base (object_columns_base const& x) : context (), //@@ -Wextra column_prefix_ (x.column_prefix_), + section_ (x.section_), root_ (0), traverse_poly_base_ (x.traverse_poly_base_), first_ (x.first_), @@ -274,6 +288,8 @@ protected: data_member_path member_path_; data_member_scope member_scope_; + object_section* section_; + protected: semantics::data_member* id () const diff --git a/odb/context.cxx b/odb/context.cxx index 760d653..a4609df 100644 --- a/odb/context.cxx +++ b/odb/context.cxx @@ -291,6 +291,179 @@ translate (string const& obj, string const& val) const return r; } +// Sections. +// +main_section_type main_section; + +bool main_section_type:: +compare (object_section const& s) const +{ + main_section_type const* ms (dynamic_cast (&s)); + return ms != 0 && *this == *ms; +} + +bool user_section:: +compare (object_section const& s) const +{ + user_section const* us (dynamic_cast (&s)); + return us != 0 && *this == *us; +} + +user_section* user_section:: +total_base () const +{ + if (base != 0) + { + semantics::class_* poly_root (context::polymorphic (*object)); + if (poly_root != 0 && poly_root != *object) + return base; + } + + return 0; +} + +size_t user_sections:: +count (unsigned short f) const +{ + size_t r (0); + + semantics::class_* poly_root (context::polymorphic (*object)); + bool poly_derived (poly_root != 0 && poly_root != object); + + if (poly_derived && (f & count_total) != 0) + r = context::polymorphic_base (*object).get ( + "user-sections").count (f); + + for (const_iterator i (begin ()); i != end (); ++i) + { + // Skip special sections unless we were explicitly asked to count them. + // + if (i->special == user_section::special_version && + (f & count_special_version) == 0) + continue; + + bool ovd (i->base != 0 && poly_derived); + + if (i->load != user_section::load_eager) + { + if (i->load_empty ()) + { + if ((f & count_load_empty) != 0) + { + if (ovd) + { + if ((f & count_override) != 0) + { + r++; + continue; // Count each section only once. + } + } + else + { + if ((f & count_new) != 0 || (f & count_total) != 0) + { + r++; + continue; + } + } + } + } + else + { + if ((f & count_load) != 0) + { + if (ovd) + { + if ((f & count_override) != 0) + { + r++; + continue; + } + } + else + { + if ((f & count_new) != 0 || (f & count_total) != 0) + { + r++; + continue; + } + } + } + } + } + + if (i->update_empty ()) + { + if ((f & count_update_empty) != 0) + { + if (ovd) + { + if ((f & count_override) != 0) + { + r++; + continue; + } + } + else + { + if ((f & count_new) != 0 || (f & count_total) != 0) + { + r++; + continue; + } + } + } + } + else + { + if ((f & count_update) != 0) + { + if (ovd) + { + if ((f & count_override) != 0) + { + r++; + continue; + } + } + else + { + if ((f & count_new) != 0 || (f & count_total) != 0) + { + r++; + continue; + } + } + } + } + + if (i->optimistic ()) + { + if ((f & count_optimistic) != 0) + { + if (ovd) + { + if ((f & count_override) != 0) + { + r++; + continue; + } + } + else + { + if ((f & count_new) != 0 || (f & count_total) != 0) + { + r++; + continue; + } + } + } + } + } + + return r; +} + // // context // @@ -2181,6 +2354,11 @@ namespace { struct column_count_impl: object_members_base { + column_count_impl (object_section* section = 0) + : object_members_base (false, section) + { + } + virtual void traverse_pointer (semantics::data_member& m, semantics::class_& c) { @@ -2189,7 +2367,14 @@ namespace object_members_base::traverse_pointer (m, c); if (context::inverse (m)) - c_.inverse += (c_.total - t); + { + size_t n (c_.total - t); + + c_.inverse += n; + + if (separate_update (member_path_)) + c_.separate_update -= n; + } } virtual void @@ -2197,9 +2382,11 @@ namespace { c_.total++; + bool ro (context::readonly (member_path_, member_scope_)); + if (id ()) c_.id++; - else if (context::readonly (member_path_, member_scope_)) + else if (ro) c_.readonly++; else if (context::version (m)) c_.optimistic_managed++; @@ -2208,6 +2395,12 @@ namespace // if (discriminator (m)) c_.discriminator++; + + if (separate_load (member_path_)) + c_.separate_load++; + + if (separate_update (member_path_) && !ro) + c_.separate_update++; } context::column_count_type c_; @@ -2215,24 +2408,35 @@ namespace } context::column_count_type context:: -column_count (semantics::class_& c) +column_count (semantics::class_& c, object_section* s) { - if (!c.count ("column-count")) + if (s == 0) { - column_count_impl t; + // Whole class. + // + if (!c.count ("column-count")) + { + column_count_impl t; + t.traverse (c); + c.set ("column-count", t.c_); + } + + return c.get ("column-count"); + } + else + { + column_count_impl t (s); t.traverse (c); - c.set ("column-count", t.c_); + return t.c_; } - - return c.get ("column-count"); } namespace { struct has_a_impl: object_members_base { - has_a_impl (unsigned short flags) - : object_members_base ((flags & context::include_base) != 0), + has_a_impl (unsigned short flags, object_section* s) + : object_members_base ((flags & context::include_base) != 0, s), r_ (0), flags_ (flags) { @@ -2244,6 +2448,20 @@ namespace return r_; } + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section if requested. + // + return section_ == 0 || + *section_ == s || + ((flags_ & include_eager_load) != 0 && + *section_ == main_section && + !s.separate_load ()); + } + virtual void traverse_pointer (semantics::data_member& m, semantics::class_&) { @@ -2275,6 +2493,7 @@ namespace context::test_straight_container | context::test_inverse_container | context::test_readonly_container | + context::test_readwrite_container | context::test_smart_container)); if (context::is_a (member_path_, @@ -2325,6 +2544,7 @@ is_a (data_member_path const& mp, test_straight_container | test_inverse_container | test_readonly_container | + test_readwrite_container | test_smart_container)) != 0 && (c = container (m)) != 0) { @@ -2340,6 +2560,9 @@ is_a (data_member_path const& mp, if (f & test_readonly_container) r = r || readonly (mp, ms); + if (f & test_readwrite_container) + r = r || (!inverse (m, kp) && !readonly (mp, ms)); + if (f & test_smart_container) r = r || (!inverse (m, kp) && !unordered (m) && container_smart (*c)); } @@ -2348,9 +2571,9 @@ is_a (data_member_path const& mp, } size_t context:: -has_a (semantics::class_& c, unsigned short flags) +has_a (semantics::class_& c, unsigned short flags, object_section* s) { - has_a_impl impl (flags); + has_a_impl impl (flags, s); impl.dispatch (c); return impl.result (); } diff --git a/odb/context.hxx b/odb/context.hxx index 306d2e4..862b74e 100644 --- a/odb/context.hxx +++ b/odb/context.hxx @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -267,6 +268,236 @@ struct model_version bool open; }; +// Sections. +// +struct object_section +{ + virtual bool + compare (object_section const&) const = 0; + + virtual bool + separate_load () const = 0; + + virtual bool + separate_update () const = 0; +}; + +inline bool +operator== (object_section const& x, object_section const& y) +{ + return x.compare (y); +} + +inline bool +operator!= (object_section const& x, object_section const& y) +{ + return !x.compare (y); +} + +// Main section. +// +struct main_section_type: object_section +{ + virtual bool + compare (object_section const& s) const; + + virtual bool + separate_load () const {return false;} + + virtual bool + separate_update () const {return false;} +}; + +inline bool +operator== (main_section_type const&, main_section_type const&) +{ + return true; // There is only one main section. +} + +extern main_section_type main_section; + +// User-defined section. +// +struct user_section: object_section +{ + enum load_type + { + load_eager, + load_lazy + }; + + enum update_type + { + update_always, + update_change, + update_manual + }; + + enum special_type + { + special_ordinary, + special_version // Fake section for optimistic version update. + }; + + user_section (semantics::data_member& m, + semantics::class_& o, + std::size_t i, + load_type l, + update_type u, + special_type s = special_ordinary) + : member (&m), object (&o), base (0), index (i), + load (l), update (u), special (s), + total (0), inverse (0), readonly (0), + containers (false), readwrite_containers (false) {} + + virtual bool + compare (object_section const& s) const; + + virtual bool + separate_load () const {return load != load_eager;} + + virtual bool + separate_update () const + { + // A separately-loaded section is always separately-updated since + // it might not be loaded when update is requested. + // + return separate_load () || update != update_always; + } + + bool + load_empty () const; + + bool + update_empty () const; + + bool + empty () const + { + return load_empty () && update_empty (); + } + + // A section is optimistic if the object that contains it is optimistic. + // For polymorphic hierarchies, only sections contained in the root are + // considered optimistic. + // + bool + optimistic () const; + + semantics::data_member* member; // Data member of this section. + semantics::class_* object; // Object containing this section. + user_section* base; // Base of this section. + std::size_t index; // Index of this sections. + + load_type load; + update_type update; + special_type special; + + // Column counts. + // + std::size_t total; + std::size_t inverse; + std::size_t readonly; + + bool containers; + bool readwrite_containers; + + // Total counts across overrides. + // + std::size_t + total_total () const + { + user_section* b (total_base ()); + return total + (b == 0 ? 0 : b->total_total ()); + } + + std::size_t + total_inverse () const + { + user_section* b (total_base ()); + return inverse + (b == 0 ? 0 : b->total_inverse ()); + } + + std::size_t + total_readonly () const + { + user_section* b (total_base ()); + return readonly + (b == 0 ? 0 : b->total_readonly ()); + } + + bool + total_containers () + { + user_section* b (total_base ()); + return containers || (b != 0 && b->total_containers ()); + } + + bool + total_readwrite_containers () + { + user_section* b (total_base ()); + return readwrite_containers || + (b != 0 && b->total_readwrite_containers ()); + } + +private: + user_section* + total_base () const; +}; + +inline bool +operator== (user_section const& x, user_section const& y) +{ + return x.member == y.member; +} + +// Using list for pointer for element stability (see user_section::base). +// +struct user_sections: std::list +{ + // Count sections that have something to load. + // + static unsigned short const count_load = 0x01; + + // Count sections that are non-eager but have nothing to load. + // + static unsigned short const count_load_empty = 0x02; + + // Count sections that have something to update. + // + static unsigned short const count_update = 0x04; + + // Count sections that have nothing to update. + // + static unsigned short const count_update_empty = 0x08; + + // Count sections that are optimistic. + // + static unsigned short const count_optimistic = 0x10; + + // Don't exclude fake optimistic version update section from the count. + // + static unsigned short const count_special_version = 0x20; + + // Count all sections, including special. + // + static unsigned short const count_all = count_update | + count_update_empty | + count_special_version; + + static unsigned short const count_new = 0x1000; + static unsigned short const count_override = 0x2000; + static unsigned short const count_total = 0x4000; + + std::size_t + count (unsigned short flags) const; + + user_sections (semantics::class_& o): object (&o) {}; + semantics::class_* object; +}; + +// Context. +// class context { public: @@ -555,6 +786,12 @@ public: return m.count ("version"); } + static bool + version (const data_member_path& mp) + { + return mp.size () == 1 && mp.back ()->count ("version"); + } + // Polymorphic inheritance. Return root of the hierarchy or NULL if // not polymorphic. // @@ -605,6 +842,51 @@ public: return unit.get ("model-version"); } + // Object sections. + // + static object_section& + section (semantics::data_member& m) + { + object_section* s (m.get ("section", 0)); + return s == 0 ? main_section : *s; + } + + static object_section& + section (data_member_path const& mp) + { + // The direct member of the object specifies the section. + // + return section (*mp.front ()); + } + + // Member belongs to a section that is loaded separately. + // + static bool + separate_load (semantics::data_member& m) + { + return section (m).separate_load (); + } + + static bool + separate_load (data_member_path const& mp) + { + return section (mp).separate_load (); + } + + // Member belongs to a section that is updated separately. + // + static bool + separate_update (semantics::data_member& m) + { + return section (m).separate_update (); + } + + static bool + separate_update (data_member_path const& mp) + { + return section (mp).separate_update (); + } + // // typedef ::class_kind class_kind_type; @@ -813,7 +1095,9 @@ public: inverse (0), readonly (0), optimistic_managed (0), - discriminator (0) + discriminator (0), + separate_load (0), + separate_update (0) { } @@ -823,10 +1107,13 @@ public: size_t readonly; size_t optimistic_managed; size_t discriminator; + + size_t separate_load; + size_t separate_update; // Only readwrite. }; static column_count_type - column_count (semantics::class_&); + column_count (semantics::class_&, object_section* = 0); static semantics::data_member* id_member (semantics::class_& c) @@ -932,7 +1219,7 @@ public: return false; } - // The 'is a' and 'has a' tests. The has_a test currently does not + // The 'is a' and 'has a' tests. The has_a() test currently does not // cross the container boundaries. // public: @@ -943,7 +1230,13 @@ public: static unsigned short const test_straight_container = 0x10; static unsigned short const test_inverse_container = 0x20; static unsigned short const test_readonly_container = 0x40; - static unsigned short const test_smart_container = 0x80; + static unsigned short const test_readwrite_container = 0x80; + static unsigned short const test_smart_container = 0x100; + + // 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; // By default the test goes into bases for non-polymorphic // hierarchies and doesn't go for polymorphic. The following @@ -971,7 +1264,7 @@ public: // a bool value (0 means no match). // size_t - has_a (semantics::class_&, unsigned short flags); + has_a (semantics::class_&, unsigned short flags, object_section* = 0); public: // Process include path by adding the prefix, putting it through @@ -1193,4 +1486,6 @@ has (Y& y) return false; } +#include + #endif // ODB_CONTEXT_HXX diff --git a/odb/context.ixx b/odb/context.ixx new file mode 100644 index 0000000..285d364 --- /dev/null +++ b/odb/context.ixx @@ -0,0 +1,27 @@ +// file : odb/context.ixx +// copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +inline bool user_section:: +load_empty () const +{ + return !separate_load () || (total == 0 && !containers && !optimistic ()); +} + +inline bool user_section:: +update_empty () const +{ + return total == inverse + readonly && !readwrite_containers; +} + +inline bool user_section:: +optimistic () const +{ + if (!context::optimistic (*object)) + return false; + else + { + semantics::class_* poly_root (context::polymorphic (*object)); + return poly_root == 0 || poly_root == object; + } +} diff --git a/odb/features.hxx b/odb/features.hxx index bc3a9c8..e7c1cdb 100644 --- a/odb/features.hxx +++ b/odb/features.hxx @@ -19,6 +19,7 @@ struct features bool polymorphic_object; bool no_id_object; bool session_object; + bool section; bool view; }; diff --git a/odb/header.cxx b/odb/header.cxx index 9af82c8..f09c1e8 100644 --- a/odb/header.cxx +++ b/odb/header.cxx @@ -60,6 +60,8 @@ traverse_object (type& c) bool abst (abstract (c)); bool reuse_abst (abst && !poly); + user_sections& uss (c.get ("user-sections")); + string const& type (class_fq_name (c)); os << "// " << class_name (c) << endl @@ -384,27 +386,37 @@ traverse_object (type& c) os << "void (*erase2) (database&, const object_type&" << (poly ? ", bool, bool" : "") << ");"; + + // Sections. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "bool (*load_section) (connection&, object_type&, section&" << + (poly ? ", const info_type*" : "") << ");"; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "bool (*update_section) (connection&, const object_type&, " << + "const section&" << (poly ? ", const info_type*" : "") << ");"; } if (options.generate_query ()) { if (!options.omit_unprepared ()) - os << "result (*query) (database&, const query_base_type&);" - << endl; + os << "result (*query) (database&, const query_base_type&);"; os << "unsigned long long (*erase_query) (database&, " << - "const query_base_type&);" - << endl; + "const query_base_type&);"; if (options.generate_prepared ()) { os << "odb::details::shared_ptr " << - "(*prepare_query) (connection&, const char*, const query_base_type&);" - << endl; + "(*prepare_query) (connection&, const char*, const query_base_type&);"; os << "odb::details::shared_ptr (*execute_query) (" - "prepared_query_impl&);" - << endl; + "prepared_query_impl&);"; } } @@ -461,6 +473,22 @@ traverse_object (type& c) os << "static void" << endl << "erase (database&, const object_type&);" << endl; + + // Sections. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "static bool" << endl + << "load (connection&, object_type&, section&);" + << endl; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "static bool" << endl + << "update (connection&, const object_type&, const section&);" + << endl; } if (options.generate_query ()) diff --git a/odb/inline.cxx b/odb/inline.cxx index d6b8c32..036a728 100644 --- a/odb/inline.cxx +++ b/odb/inline.cxx @@ -107,6 +107,8 @@ traverse_object (type& c) bool abst (abstract (c)); bool reuse_abst (abst && !poly); + user_sections& uss (c.get ("user-sections")); + string const& type (class_fq_name (c)); string traits ("access::object_traits< " + type + " >"); @@ -269,6 +271,30 @@ traverse_object (type& c) << "function_table[db.id ()]->erase2 (db, o" << (poly ? ", true, true" : "") << ");" << "}"; + + // Sections. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "inline" << endl + << "bool " << traits << "::" << endl + << "load (connection& c, object_type& o, section& s)" + << "{" + << "return function_table[c.database ().id ()]->load_section (" << + "c, o, s" << (poly ? ", 0" : "") << ");" + << "}"; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "inline" << endl + << "bool " << traits << "::" << endl + << "update (connection& c, const object_type& o, const section& s)" + << "{" + << "return function_table[c.database ().id ()]->update_section (" << + "c, o, s" << (poly ? ", 0" : "") << ");" + << "}"; } if (options.generate_query ()) diff --git a/odb/pragma.cxx b/odb/pragma.cxx index 13ebeb0..2b4dc3d 100644 --- a/odb/pragma.cxx +++ b/odb/pragma.cxx @@ -386,6 +386,9 @@ check_spec_decl_type (declaration const& d, p == "auto" || p == "column" || p == "inverse" || + p == "section" || + p == "load" || + p == "update" || p == "version" || p == "index" || p == "unique" || @@ -430,7 +433,8 @@ check_spec_decl_type (declaration const& d, p == "object" || p == "optimistic" || p == "polymorphic" || - p == "definition") + p == "definition" || + p == "sectionable") { if (tc != RECORD_TYPE) { @@ -1405,6 +1409,18 @@ handle_pragma (cxx_lexer& l, val = l.location (); tt = l.next (tl, &tn); } + else if (p == "sectionable") + { + // sectionable + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = l.next (tl, &tn); + } else if (p == "callback") { // callback (name) @@ -2112,6 +2128,121 @@ handle_pragma (cxx_lexer& l, tt = l.next (tl, &tn); } + else if (p == "section") + { + // section (name) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_NAME) + { + error (l) << "member name expected in db pragma " << p << endl; + return; + } + + name = "section-member"; + val = tl; + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "load") + { + // load (eager|lazy) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_NAME || (tl != "eager" && tl != "lazy")) + { + error (l) << "eager or lazy expected in db pragma " << p << endl; + return; + } + + name = "section-load"; + val = (tl == "eager" + ? user_section::load_eager + : user_section::load_lazy); + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } + else if (p == "update") + { + // update (always|change|manual) + // + + // Make sure we've got the correct declaration type. + // + if (decl && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + if (l.next (tl, &tn) != CPP_OPEN_PAREN) + { + error (l) << "'(' expected after db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + + if (tt != CPP_NAME || + (tl != "always" && tl != "change" && tl != "manual")) + { + error (l) << "always, change, or manual expected in db pragma " << + p << endl; + return; + } + + name = "section-update"; + + if (tl == "always") + val = user_section::update_always; + else if (tl == "change") + val = user_section::update_change; + else + val = user_section::update_manual; + + if (l.next (tl, &tn) != CPP_CLOSE_PAREN) + { + error (l) << "')' expected at the end of db pragma " << p << endl; + return; + } + + tt = l.next (tl, &tn); + } else if (p == "unordered") { // unordered @@ -3055,6 +3186,9 @@ handle_pragma_qualifier (cxx_lexer& l, string p) p == "value_null" || p == "value_not_null" || p == "default" || + p == "section" || + p == "load" || + p == "update" || p == "inverse" || p == "unordered" || p == "readonly" || @@ -3400,6 +3534,24 @@ handle_pragma_db_default (cpp_reader* r) } extern "C" void +handle_pragma_db_section (cpp_reader* r) +{ + handle_pragma_qualifier (r, "section"); +} + +extern "C" void +handle_pragma_db_load (cpp_reader* r) +{ + handle_pragma_qualifier (r, "load"); +} + +extern "C" void +handle_pragma_db_update (cpp_reader* r) +{ + handle_pragma_qualifier (r, "update"); +} + +extern "C" void handle_pragma_db_inverse (cpp_reader* r) { handle_pragma_qualifier (r, "inverse"); @@ -3503,6 +3655,9 @@ register_odb_pragmas (void*, void*) c_register_pragma_with_expansion ("db", "value_null", handle_pragma_db_value_null); c_register_pragma_with_expansion ("db", "value_not_null", handle_pragma_db_value_not_null); c_register_pragma_with_expansion ("db", "default", handle_pragma_db_default); + c_register_pragma_with_expansion ("db", "section", handle_pragma_db_section); + c_register_pragma_with_expansion ("db", "load", handle_pragma_db_load); + c_register_pragma_with_expansion ("db", "update", handle_pragma_db_update); c_register_pragma_with_expansion ("db", "inverse", handle_pragma_db_inverse); c_register_pragma_with_expansion ("db", "unordered", handle_pragma_db_unordered); c_register_pragma_with_expansion ("db", "readonly", handle_pragma_db_readonly); diff --git a/odb/processor.cxx b/odb/processor.cxx index 4e50efe..bc6c408 100644 --- a/odb/processor.cxx +++ b/odb/processor.cxx @@ -5,6 +5,7 @@ #include #include +#include // std::find #include #include @@ -37,8 +38,65 @@ namespace if (transient (m)) return; - process_access (m, "get"); - process_access (m, "set"); + semantics::names* hint; + semantics::type& t (utype (m, hint)); + + // See if this member is a section. + // + if (t.fq_name () == "::odb::section") + { + using semantics::class_; + + class_& c (dynamic_cast (m.scope ())); + class_* poly_root (polymorphic (c)); + semantics::data_member* opt (optimistic (c)); + + // If we have sections in a polymorphic optimistic hierarchy, + // then the version member should be in the root. + // + if (poly_root == &c && opt != 0 && &opt->scope () != &c) + { + error (m.location ()) << "version must be a direct data member " << + "of a class that contains sections" << endl; + info (opt->location ()) << "version member is declared here" << endl; + throw operation_failed (); + } + + process_user_section (m, c); + + // We don't need a modifier but the accessor should be by-reference. + // + process_access (m, "get"); + + member_access& ma (m.get ("get")); + if (ma.by_value) + { + error (ma.loc) << "accessor returning a value cannot be used " + << "for a section" << endl; + info (ma.loc) << "accessor returning a const reference is required" + << endl; + info (m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + + // Mark this member as transient since we don't store it in the + // database. + // + m.set ("transient", true); + + features.section = true; + return; + } + else + { + process_access (m, "get"); + process_access (m, "set"); + } + + // See if this member belongs to a section. + // + if (m.count ("section-member") != 0) + process_section_member (m); // We don't need to do any further processing for common if we // are generating static multi-database code. @@ -46,9 +104,6 @@ namespace if (multi_static && options.database ()[0] == database::common) return; - semantics::names* hint; - semantics::type& t (utype (m, hint)); - // Handle wrappers. // semantics::type* wt (0), *qwt (0); @@ -587,6 +642,182 @@ namespace } // + // Process section. + // + + user_section& + process_user_section (semantics::data_member& m, semantics::class_& c) + { + user_sections& uss (c.get ("user-sections")); + + user_section::load_type l ( + m.get ("section-load", user_section::load_eager)); + + user_section::update_type u ( + m.get ("section-update", user_section::update_always)); + + if (l == user_section::load_eager && u == user_section::update_always) + { + location const& l (m.location ()); + + error (l) << "eager-loaded, always-updated section is pointless" + << endl; + + info (l) << "use '#pragma db load' and/or '#pragma db update' to " + "specify an alternative loading and/or updating strategy" << endl; + + info (l) << "or remove the section altogether" << endl; + + throw operation_failed (); + } + + size_t n (uss.count (user_sections::count_total | + user_sections::count_all)); + user_section us (m, c, n, l, u); + + // We may already have seen this section (e.g., forward reference + // from a member of this section). + // + user_sections::iterator i (find (uss.begin (), uss.end (), us)); + + if (i != uss.end ()) + return *i; + + // If we are adding a new section to an optimistic class with + // version in a base, make sure the base is sectionable. + // + semantics::data_member* opt (optimistic (c)); + if (opt != 0 && &opt->scope () != &c) + { + semantics::class_* poly_root (polymorphic (c)); + semantics::node* base (poly_root ? poly_root : &opt->scope ()); + + if (!base->count ("sectionable")) + { + error (m.location ()) << "adding new section to a derived class " << + "in an optimistic hierarchy requires sectionable base class" << + endl; + + info (base->location ()) << "use '#pragma db object sectionable' " << + "to make the base class of this hierarchy sectionable" << endl; + + throw operation_failed (); + } + } + + uss.push_back (us); + return uss.back (); + } + + void + process_section_member (semantics::data_member& m) + { + using semantics::class_; + using semantics::data_member; + + string name (m.get ("section-member")); + location_t loc (m.get ("section-member-location")); + class_& c (dynamic_cast (m.scope ())); + + class_* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + try + { + data_member& us (c.lookup (name, class_::include_hidden)); + + // Make sure we are referencing a section. + // + if (utype (us).fq_name () != "::odb::section") + { + error (loc) << "data member '" << name << "' in '#pragma db " << + "section' is not of the odb::section type" << endl; + throw operation_failed (); + } + + // If the section is in the base, handle polymorphic inheritance. + // + class_& b (dynamic_cast (us.scope ())); + object_section* s (0); + + if (&c != &b && poly_derived) + { + user_sections& uss (c.get ("user-sections")); + + // This is a section override. See if we have already handled + // this section. + // + for (user_sections::iterator i (uss.begin ()); + s == 0 && i != uss.end (); + ++i) + { + if (i->member == &us) + s = &*i; + } + + // Otherwise, find and copy the nearest override in the base. + // The result should be a chain of overrides leading all the + // way to the original section. + // + if (s == 0) + { + for (class_* b (&polymorphic_base (c));; + b = &polymorphic_base (*b)) + { + user_sections& buss (b->get ("user-sections")); + + for (user_sections::iterator i (buss.begin ()); + s == 0 && i != buss.end (); + ++i) + { + if (i->member == &us) + { + uss.push_back (*i); + uss.back ().object = &c; + uss.back ().base = &*i; + s = &uss.back (); + } + } + + if (s != 0) + break; + + assert (b != poly_root); // We should have found it by now. + } + } + } + else + s = &process_user_section (us, c); + + m.set ("section", s); // Insert as object_section. + } + catch (semantics::unresolved const& e) + { + if (e.type_mismatch) + error (loc) << "name '" << name << "' in '#pragma db section' " << + "does not refer to a data member" << endl; + else + error (loc) << "unable to resolve data member '" << name << "' " << + "specified with '#pragma db section'" << endl; + + throw operation_failed (); + } + catch (semantics::ambiguous const& e) + { + error (loc) << "data member name '" << name << "' specified " << + "with '#pragma db section' is ambiguous" << endl; + + info (e.first.named ().location ()) << "could resolve to this " << + "data member" << endl; + + info (e.second.named ().location ()) << "or could resolve to " << + "this data member" << endl; + + throw operation_failed (); + } + } + + // // Process wrapper. // @@ -1040,10 +1271,28 @@ namespace data_member& im ( c->lookup (name, class_::include_hidden)); + if (im.count ("transient")) + { + error (loc) << "data member '" << name << "' specified with " << + "'#pragma db inverse' is transient" << endl; + info (im.location ()) << "data member '" << name << "' is " << + "defined here" << endl; + throw operation_failed (); + } + + if (im.count ("inverse") || im.count ("value-inverse")) + { + error (loc) << "data member '" << name << "' specified with " << + "'#pragma db inverse' is inverse" << endl; + info (im.location ()) << "data member '" << name << "' is " << + "defined here" << endl; + throw operation_failed (); + } + // @@ Would be good to check that the other end is actually - // an object pointer, is not marked as transient or inverse, - // and points to the correct object. But the other class may - // not have been processed yet. + // an object pointer and points to the correct object. But + // the other class may not have been processed yet. Need to + // do in validator, pass 2. // m.remove ("inverse"); m.set (kp + (kp.empty () ? "": "-") + "inverse", &im); @@ -1413,6 +1662,8 @@ namespace // m.set ("id-tree-type", &id_tree_type); + // Has to be first to handle inverse. + // process_container_value (*vt, m, "value", true); if (it != 0) @@ -1643,11 +1894,14 @@ namespace assign_pointer (c); if (k == class_object) - traverse_object (c); + traverse_object_pre (c); else if (k == class_view) traverse_view (c); names (c); + + if (k == class_object) + traverse_object_post (c); } // @@ -1655,10 +1909,45 @@ namespace // virtual void - traverse_object (type& c) + traverse_object_pre (type& c) { semantics::class_* poly_root (polymorphic (c)); + // Sections. + // + user_sections& uss (c.set ("user-sections", user_sections (c))); + + // Copy sections from reuse bases. For polymorphic classes, sections + // are overridden. + // + if (poly_root == 0 || poly_root == &c) + { + for (type::inherits_iterator i (c.inherits_begin ()); + i != c.inherits_end (); ++i) + { + type& b (i->base ()); + + if (object (b)) + { + user_sections& buss (b.get ("user-sections")); + + for (user_sections::iterator j (buss.begin ()); + j != buss.end (); + ++j) + { + // Don't copy the special version update section. + // + if (j->special == user_section::special_version) + continue; + + uss.push_back (*j); + uss.back ().object = &c; + uss.back ().base = &*j; + } + } + } + } + // Determine whether it is a session object. // if (!c.count ("session")) @@ -1792,6 +2081,69 @@ namespace } } + virtual void + traverse_object_post (type& c) + { + semantics::class_* poly_root (polymorphic (c)); + semantics::data_member* opt (optimistic (c)); + + // Sections. + // + user_sections& uss (c.get ("user-sections")); + + // See if we need to add a special fake section for version update. + // + if (c.count ("sectionable")) + { + uss.push_back ( + user_section (*opt, + c, + uss.count (user_sections::count_total | + user_sections::count_all), + user_section::load_lazy, + user_section::update_manual, + user_section::special_version)); + + // If we are a root of a polymorphic hierarchy and the version is in + // a reuse-base, then we need to make sure that base is sectionable + // and derive from its special version update section. + // + semantics::node& opt_base (opt->scope ()); + if (poly_root == &c && &opt_base != &c) + { + if (!opt_base.count ("sectionable")) + { + location_t l (c.get ("sectionable-location")); + + error (l) << "reuse base class of a sectionable polymorphic " << + "root class must be sectionable" << endl; + + info (opt_base.location ()) << "use '#pragma db object " << + "sectionable' to make the base class of this hierarchy " << + "sectionable" << endl; + + throw operation_failed (); + } + + uss.back ().base = + &opt_base.get ("user-sections").back (); + } + } + + // Calculate column counts for sections. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + column_count_type cc (column_count (c, &*i)); + i->total = cc.total; + i->inverse = cc.inverse; + i->readonly = cc.readonly; + + if ((i->containers = has_a (c, test_container, &*i))) + i->readwrite_containers = has_a (c, test_readwrite_container, &*i); + } + } + // // View. // diff --git a/odb/relational/common.hxx b/odb/relational/common.hxx index 1e6a319..abe5e31 100644 --- a/odb/relational/common.hxx +++ b/odb/relational/common.hxx @@ -19,21 +19,25 @@ namespace relational member_base (semantics::type* type, string const& fq_type, - string const& key_prefix) + string const& key_prefix, + object_section* section = 0) : type_override_ (type), fq_type_override_ (fq_type), - key_prefix_ (key_prefix) + key_prefix_ (key_prefix), + section_ (section) { } member_base (string const& var, semantics::type* type, string const& fq_type, - string const& key_prefix) + string const& key_prefix, + object_section* section = 0) : var_override_ (var), type_override_ (type), fq_type_override_ (fq_type), - key_prefix_ (key_prefix) + key_prefix_ (key_prefix), + section_ (section) { } @@ -47,6 +51,7 @@ namespace relational semantics::type* type_override_; string fq_type_override_; string key_prefix_; + object_section* section_; }; // Template argument is the database SQL type (sql_type). diff --git a/odb/relational/context.cxx b/odb/relational/context.cxx index c8c0ae2..b525802 100644 --- a/odb/relational/context.cxx +++ b/odb/relational/context.cxx @@ -103,7 +103,7 @@ namespace relational } bool context:: - grow_impl (semantics::class_&) + grow_impl (semantics::class_&, user_section*) { return false; } diff --git a/odb/relational/context.hxx b/odb/relational/context.hxx index 02c4b9c..d0211c6 100644 --- a/odb/relational/context.hxx +++ b/odb/relational/context.hxx @@ -75,10 +75,12 @@ namespace relational { public: // Return true if an object or value type has members for which - // the image can grow. + // the image can grow. If section is not specified, then ignore + // separately loaded members. Otherwise ignore members that do + // not belong to the section. // bool - grow (semantics::class_&); + grow (semantics::class_&, user_section* = 0); // The same for a member's value type. // @@ -206,7 +208,7 @@ namespace relational // The default implementation returns false. // virtual bool - grow_impl (semantics::class_&); + grow_impl (semantics::class_&, user_section*); virtual bool grow_impl (semantics::data_member&); diff --git a/odb/relational/context.ixx b/odb/relational/context.ixx index afc93da..1f959de 100644 --- a/odb/relational/context.ixx +++ b/odb/relational/context.ixx @@ -5,9 +5,9 @@ namespace relational { inline bool context:: - grow (semantics::class_& c) + grow (semantics::class_& c, user_section* s) { - return current ().grow_impl (c); + return current ().grow_impl (c, s); } inline bool context:: diff --git a/odb/relational/header.cxx b/odb/relational/header.cxx index 04a73a2..da7f80c 100644 --- a/odb/relational/header.cxx +++ b/odb/relational/header.cxx @@ -29,6 +29,10 @@ traverse_object (type& c) string const& type (class_fq_name (c)); column_count_type const& cc (column_count (c)); + // Sections. + // + user_sections& uss (c.get ("user-sections")); + os << "// " << class_name (c) << endl << "//" << endl; @@ -109,13 +113,18 @@ traverse_object (type& c) { if (base_id) { - semantics::class_& b ( - dynamic_cast (id->scope ())); - string const& type (); + if (poly_derived) + os << "typedef root_traits::id_image_type id_image_type;" + << endl; + else + { + semantics::class_& b ( + dynamic_cast (id->scope ())); - os << "typedef object_traits_impl< " << class_fq_name (b) << ", " << - "id_" << db << " >::id_image_type id_image_type;" - << endl; + os << "typedef object_traits_impl< " << class_fq_name (b) << ", " << + "id_" << db << " >::id_image_type id_image_type;" + << endl; + } } else { @@ -147,6 +156,12 @@ traverse_object (type& c) // image_type_->traverse (c); + // Extra (container, section) statement cache (forward declaration). + // + if (!reuse_abst && id != 0) + os << "struct extra_statement_cache_type;" + << endl; + // // Containers (abstract and concrete). // @@ -157,6 +172,16 @@ traverse_object (type& c) } // + // Sections (abstract and concrete). + // + + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + instance t (c); + t->traverse (*i); + } + + // // Query (abstract and concrete). // @@ -352,11 +377,9 @@ traverse_object (type& c) // Containers (concrete). // - // Statement cache (forward declaration). // - if (id != 0) - os << "struct container_statement_cache_type;" - << endl; + // Sections (concrete). + // // column_count // @@ -373,7 +396,12 @@ traverse_object (type& c) os << "static const std::size_t discriminator_column_count = " << cc.discriminator << "UL;"; - os << endl; + os << endl + << "static const std::size_t separate_load_column_count = " << + cc.separate_load << "UL;" + << "static const std::size_t separate_update_column_count = " << + cc.separate_update << "UL;" + << endl; // Statements. // @@ -396,7 +424,7 @@ traverse_object (type& c) os << "static const char find_discriminator_statement[];"; } - if (cc.total != cc.id + cc.inverse + cc.readonly) + if (cc.total != cc.id + cc.inverse + cc.readonly + cc.separate_update) os << "static const char update_statement[];"; os << "static const char erase_statement[];"; @@ -499,6 +527,28 @@ traverse_object (type& c) os << ");" << endl; + + // Sections. + // + // We treat all polymorphic sections as (potentially) having something + // to load or to update since we cannot predict what will be added to + // them in overrides. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "static bool" << endl + << "load (connection&, object_type&, section&" << + (poly ? ", const info_type* = 0" : "") << ");" + << endl; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "static bool" << endl + << "update (connection&, const object_type&, const section&" << + (poly ? ", const info_type* = 0" : "") << ");" + << endl; } // query () @@ -578,14 +628,16 @@ traverse_object (type& c) << "load_ ("; if (poly && !poly_derived) - os << "base_statements_type&, "; + os << "base_statements_type&," << endl; else - os << "statements_type&, "; + os << "statements_type&," << endl; - os << "object_type&"; + os << "object_type&," << endl + << "bool reload = false"; if (poly_derived) - os << ", std::size_t = depth"; + os << "," << endl + << "std::size_t = depth"; os << ");" << endl; diff --git a/odb/relational/header.hxx b/odb/relational/header.hxx index de2cfe8..4616543 100644 --- a/odb/relational/header.hxx +++ b/odb/relational/header.hxx @@ -854,6 +854,204 @@ namespace relational semantics::class_& c_; }; + // + // + struct section_traits: virtual context + { + typedef section_traits base; + + section_traits (semantics::class_& c): c_ (c) {} + + virtual void + section_public_extra_pre (user_section&) + { + } + + virtual void + section_public_extra_post (user_section&) + { + } + + virtual void + traverse (user_section& s) + { + semantics::class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c_); + + semantics::data_member* opt (optimistic (c_)); + + // Treat the special version update sections as abstract in reuse + // inheritance. + // + bool reuse_abst (!poly && + (abstract (c_) || + s.special == user_section::special_version)); + + bool load (s.total != 0 && s.separate_load ()); + bool load_con (s.containers && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_con (s.readwrite_containers); + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + // Don't generate anything for empty sections. + // + if (!(load || load_con || load_opt || + update || update_con || update_opt)) + return; + + // If we are adding a new section to a derived class in an optimistic + // hierarchy, then pretend it inherits from the special version update + // section. + // + user_section* rs (0); + if (opt != 0) + { + // Skip overrides and get to the new section if polymorphic. + // + for (rs = &s; poly && rs->base != 0; rs = rs->base) ; + + if (rs != 0) + { + if (rs->object != &opt->scope ()) + rs->base = &(poly ? poly_root : &opt->scope ())-> + get ("user-sections").back (); + else + rs = 0; + } + } + + string name (public_name (*s.member) + "_traits"); + + os << "// " << s.member->name () << endl + << "//" << endl + << "struct " << name + << "{"; + + os << "typedef object_traits_impl::image_type image_type;" + << endl; + + section_public_extra_pre (s); + + // bind (id, image_type) + // + // If id is NULL, then id is ignored (select). Otherwise, it is + // copied at the end (update). + // + if (load || load_opt || update || update_opt) + os << "static std::size_t" << endl + << "bind (" << bind_vector << "," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "image_type&," << endl + << db << "::statement_kind);" + << endl; + + // grow () + // + // We have to have out own version because the truncated vector + // will have different number of elements. + // + if (generate_grow && (load || load_opt)) + os << "static bool" << endl + << "grow (image_type&, " << truncated_vector << ");" + << endl; + + // init (object, image) + // + if (load) + os << "static void" << endl + << "init (object_type&, const image_type&, database*);" + << endl; + + // init (image, object) + // + if (update) + os << "static " << (generate_grow ? "bool" : "void") << endl + << "init (image_type&, const object_type&);" + << endl; + + // The rest does not apply to reuse-abstract sections. + // + if (reuse_abst) + { + section_public_extra_post (s); + os << "};"; + return; + } + + // column_count + // + column_count_type const& cc (column_count (poly ? *poly_root : c_)); + + // Generate load and update column counts even when they are zero so + // that we can instantiate section_statements. + // + os << "static const std::size_t id_column_count = " << cc.id << "UL;"; + + os << "static const std::size_t managed_optimistic_load_column_count" << + " = " << cc.optimistic_managed << "UL;" + << "static const std::size_t load_column_count = " << + (load ? s.total_total () : 0) << "UL;"; + + os << "static const std::size_t managed_optimistic_update_column_count" << + " = " << (poly_derived ? 0 : cc.optimistic_managed) << "UL;" + << "static const std::size_t update_column_count = " << + (update ? s.total - s.inverse - s.readonly : 0) << "UL;" + << endl; + + // Statements. + // + if (load || load_opt) + os << "static const char select_statement[];" + << endl; + + if (update || update_opt) + os << "static const char update_statement[];" + << endl; + + // Section statements. + // + if (load || load_opt || update || update_opt) + os << "typedef " << db << "::section_statements< object_type, " << + name << " > statements_type;" + << endl; + + // We pass statement cache instead of just statements because + // we may also need statements for containers. + // + + // load () + // + if (load || load_opt || load_con) + os << "static void" << endl + << "load (extra_statement_cache_type&, object_type&" << + (poly ? ", bool top = true" : "") << ");" + << endl; + + // update () + // + if (update || update_opt || update_con) + os << "static void" << endl + << "update (extra_statement_cache_type&, const object_type&" << + (poly_derived && s.base != 0 ? ", bool base = true" : "") << ");" + << endl; + + section_public_extra_post (s); + + os << "};"; + + if (rs != 0) + rs->base = 0; + } + + protected: + semantics::class_& c_; + }; + // First pass over objects, views, and composites. Some code must be // split into two parts to deal with yet undefined types. // diff --git a/odb/relational/inline.hxx b/odb/relational/inline.hxx index 5f1caa7..928982e 100644 --- a/odb/relational/inline.hxx +++ b/odb/relational/inline.hxx @@ -165,10 +165,11 @@ namespace relational virtual void traverse_object (type& c) { - semantics::data_member* id (id_member (c)); - bool base_id (id && &id->scope () != &c); // Comes from base. + using semantics::data_member; - semantics::data_member* optimistic (context::optimistic (c)); + data_member* id (id_member (c)); + bool base_id (id && &id->scope () != &c); // Comes from base. + data_member* optimistic (context::optimistic (c)); // Base class that contains the object id and version for optimistic // concurrency. @@ -187,6 +188,8 @@ namespace relational string traits ("access::object_traits_impl< " + type + ", id_" + db.string () + " >"); + user_sections& uss (c.get ("user-sections")); + os << "// " << class_name (c) << endl << "//" << endl << endl; @@ -313,14 +316,60 @@ namespace relational << "erase (database& db, const object_type& obj)" << "{" << "callback (db, obj, callback_event::pre_erase);" - << "erase (db, id (obj));" - << "callback (db, obj, callback_event::post_erase);" + << "erase (db, id (obj));"; + + // Note that we don't reset sections since the object is now + // transient and the state of a section in a transient object + // is undefined. + + os << "callback (db, obj, callback_event::post_erase);" + << "}"; + } + + // load (section) [thunk version; poly_derived is true] + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0 && + uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) == 0) + { + os << "inline" << endl + << "bool " << traits << "::" << endl + << "load (connection& conn, object_type& obj, section& s, " << + "const info_type* pi)" + << "{" + << "return base_traits::load (conn, obj, s, pi);" + << "}"; + } + + // update (section) [thunk version; poly_derived is true] + // + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0 && + uss.count (user_sections::count_new | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) == 0) + { + os << "inline" << endl + << "bool " << traits << "::" << endl + << "update (connection& conn, const object_type& obj, " << + "const section& s, const info_type* pi)" + << "{" + << "return base_traits::update (conn, obj, s, pi);" << "}"; } // load_() // - if (id != 0 && !(poly_derived || has_a (c, test_container))) + if (id != 0 && + !(poly_derived || + has_a (c, test_container | include_eager_load, &main_section) || + uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0)) { os << "inline" << endl << "void " << traits << "::" << endl @@ -331,9 +380,33 @@ namespace relational else os << "statements_type&, "; - os << "object_type&)" + os << "object_type& obj, bool)" << "{" - << "}"; + << "ODB_POTENTIALLY_UNUSED (obj);" + << endl; + + // Mark eager sections as loaded. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + data_member& m (*i->member); + + // Section access is always by reference. + // + member_access& ma (m.get ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + } + + os << "}"; } if (poly && need_image_clone && options.generate_query ()) diff --git a/odb/relational/mssql/header.cxx b/odb/relational/mssql/header.cxx index 1b868fc..5601755 100644 --- a/odb/relational/mssql/header.cxx +++ b/odb/relational/mssql/header.cxx @@ -47,6 +47,32 @@ namespace relational }; entry class1_entry_; + struct section_traits: relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + section_public_extra_pre (user_section&) + { + if (abstract (c_) && !polymorphic (c_)) + return; + + // rowvesion + // + bool rv (false); + if (semantics::data_member* m = optimistic (c_)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + rv = (t.type == sql_type::ROWVERSION); + } + + os << "static const bool rowversion = " << + (rv ? "true" : "false") << ";" + << endl; + } + }; + entry section_traits_; + struct image_type: relational::image_type, context { image_type (base const& x): base (x) {}; diff --git a/odb/relational/mssql/source.cxx b/odb/relational/mssql/source.cxx index efd71f1..837313a 100644 --- a/odb/relational/mssql/source.cxx +++ b/odb/relational/mssql/source.cxx @@ -846,6 +846,53 @@ namespace relational }; entry container_traits_; + struct section_traits: relational::section_traits, + statement_columns_common + { + section_traits (base const& x): base (x) {} + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk) + { + statement_columns_common::process (cols, sk); + } + + virtual string + optimistic_version_increment (semantics::data_member& m) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type != sql_type::ROWVERSION + ? "1" + : "sts.update_statement ().version ()"; + } + + virtual void + update_statement_extra (user_section&) + { + semantics::data_member* ver (optimistic (c_)); + + if (ver == 0 || + parse_sql_type (column_type (*ver), *ver).type != + sql_type::ROWVERSION) + return; + + // Long data & SQL Server 2005 incompatibility is detected + // in persist_statement_extra. + // + os << strlit ( + " OUTPUT INSERTED." + convert_from ( + column_qname (*ver, column_prefix ()), *ver)) << endl; + } + }; + entry section_traits_; + struct class_: relational::class_, statement_columns_common { class_ (base const& x): base (x) {} @@ -1018,14 +1065,14 @@ namespace relational } virtual string - optimimistic_version_init (semantics::data_member& m) + optimistic_version_init (semantics::data_member& m) { sql_type t (parse_sql_type (column_type (m), m)); return t.type != sql_type::ROWVERSION ? "1" : "st.version ()"; } virtual string - optimimistic_version_increment (semantics::data_member& m) + optimistic_version_increment (semantics::data_member& m) { sql_type t (parse_sql_type (column_type (m), m)); return t.type != sql_type::ROWVERSION diff --git a/odb/relational/mysql/context.cxx b/odb/relational/mysql/context.cxx index 52c1dcc..75b71e9 100644 --- a/odb/relational/mysql/context.cxx +++ b/odb/relational/mysql/context.cxx @@ -145,8 +145,8 @@ namespace relational { struct has_grow: traversal::class_ { - has_grow (bool& r) - : r_ (r) + has_grow (bool& r, user_section* s) + : r_ (r), section_ (s) { *this >> inherits_ >> *this; } @@ -161,7 +161,7 @@ namespace relational if (!(context::object (c) || view || context::composite (c))) return; - if (c.count ("mysql-grow")) + if (section_ == 0 && c.count ("mysql-grow")) r_ = c.get ("mysql-grow"); else { @@ -173,29 +173,41 @@ namespace relational if (!r_) names (c); - c.set ("mysql-grow", r_); + if (section_ == 0) + c.set ("mysql-grow", r_); } } private: bool& r_; + user_section* section_; traversal::inherits inherits_; }; struct has_grow_member: member_base { has_grow_member (bool& r, + user_section* section = 0, semantics::type* type = 0, string const& key_prefix = string ()) - : relational::member_base (type, string (), key_prefix), + : relational::member_base (type, string (), key_prefix, section), r_ (r) { } + virtual bool + pre (member_info& mi) + { + return (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)); + } + virtual void traverse_composite (member_info& mi) { // By calling grow() instead of recursing, we reset any overrides. + // We also don't pass section since they don't apply inside + // composites. // r_ = r_ || context::grow (dynamic_cast (mi.t)); } @@ -236,14 +248,14 @@ namespace relational } bool context:: - grow_impl (semantics::class_& c) + grow_impl (semantics::class_& c, user_section* section) { - if (c.count ("mysql-grow")) + if (section == 0 && c.count ("mysql-grow")) return c.get ("mysql-grow"); bool r (false); - has_grow ct (r); - has_grow_member mt (r); + has_grow ct (r, section); + has_grow_member mt (r, section); traversal::names names; ct >> names >> mt; ct.traverse (c); @@ -263,7 +275,7 @@ namespace relational grow_impl (semantics::data_member& m, semantics::type& t, string const& kp) { bool r (false); - has_grow_member mt (r, &t, kp); + has_grow_member mt (r, 0, &t, kp); mt.traverse (m); return r; } diff --git a/odb/relational/mysql/context.hxx b/odb/relational/mysql/context.hxx index 5d5571e..681a56e 100644 --- a/odb/relational/mysql/context.hxx +++ b/odb/relational/mysql/context.hxx @@ -113,7 +113,7 @@ namespace relational convert_expr (string const&, semantics::data_member&, bool); virtual bool - grow_impl (semantics::class_&); + grow_impl (semantics::class_&, user_section*); virtual bool grow_impl (semantics::data_member&); diff --git a/odb/relational/oracle/source.cxx b/odb/relational/oracle/source.cxx index a0ef339..2fc2ad2 100644 --- a/odb/relational/oracle/source.cxx +++ b/odb/relational/oracle/source.cxx @@ -546,6 +546,18 @@ namespace relational }; entry container_traits_; + struct section_traits: relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + }; + entry section_traits_; + struct class_: relational::class_, context { class_ (base const& x): base (x) {} diff --git a/odb/relational/pgsql/context.cxx b/odb/relational/pgsql/context.cxx index 3d71144..947c6bd 100644 --- a/odb/relational/pgsql/context.cxx +++ b/odb/relational/pgsql/context.cxx @@ -114,8 +114,8 @@ namespace relational { struct has_grow: traversal::class_ { - has_grow (bool& r) - : r_ (r) + has_grow (bool& r, user_section* s) + : r_ (r), section_ (s) { *this >> inherits_ >> *this; } @@ -128,7 +128,7 @@ namespace relational if (!(context::object (c) || context::composite (c))) return; - if (c.count ("pgsql-grow")) + if (section_ == 0 && c.count ("pgsql-grow")) r_ = c.get ("pgsql-grow"); else { @@ -139,29 +139,41 @@ namespace relational if (!r_) names (c); - c.set ("pgsql-grow", r_); + if (section_ == 0) + c.set ("pgsql-grow", r_); } } private: bool& r_; + user_section* section_; traversal::inherits inherits_; }; struct has_grow_member: member_base { has_grow_member (bool& r, + user_section* section = 0, semantics::type* type = 0, string const& key_prefix = string ()) - : relational::member_base (type, string (), key_prefix), + : relational::member_base (type, string (), key_prefix, section), r_ (r) { } + virtual bool + pre (member_info& mi) + { + return (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)); + } + virtual void traverse_composite (member_info& mi) { // By calling grow() instead of recursing, we reset any overrides. + // We also don't pass section since they don't apply inside + // composites. // r_ = r_ || context::grow (dynamic_cast (mi.t)); } @@ -189,22 +201,15 @@ namespace relational }; } - string const& context:: - convert_expr (string const& sqlt, semantics::data_member& m, bool to) - { - sql_type const& t (parse_sql_type (sqlt, m)); - return to ? t.to : t.from; - } - bool context:: - grow_impl (semantics::class_& c) + grow_impl (semantics::class_& c, user_section* section) { - if (c.count ("pgsql-grow")) + if (section == 0 && c.count ("pgsql-grow")) return c.get ("pgsql-grow"); bool r (false); - has_grow ct (r); - has_grow_member mt (r); + has_grow ct (r, section); + has_grow_member mt (r, section); traversal::names names; ct >> names >> mt; ct.traverse (c); @@ -224,11 +229,18 @@ namespace relational grow_impl (semantics::data_member& m, semantics::type& t, string const& kp) { bool r (false); - has_grow_member mt (r, &t, kp); + has_grow_member mt (r, 0, &t, kp); mt.traverse (m); return r; } + string const& context:: + convert_expr (string const& sqlt, semantics::data_member& m, bool to) + { + sql_type const& t (parse_sql_type (sqlt, m)); + return to ? t.to : t.from; + } + string context:: database_type_impl (semantics::type& t, semantics::names* hint, diff --git a/odb/relational/pgsql/context.hxx b/odb/relational/pgsql/context.hxx index d06e932..b7ec873 100644 --- a/odb/relational/pgsql/context.hxx +++ b/odb/relational/pgsql/context.hxx @@ -103,7 +103,7 @@ namespace relational convert_expr (string const&, semantics::data_member&, bool); virtual bool - grow_impl (semantics::class_&); + grow_impl (semantics::class_&, user_section*); virtual bool grow_impl (semantics::data_member&); diff --git a/odb/relational/pgsql/header.cxx b/odb/relational/pgsql/header.cxx index b924a42..a23ec9e 100644 --- a/odb/relational/pgsql/header.cxx +++ b/odb/relational/pgsql/header.cxx @@ -36,6 +36,9 @@ namespace relational column_count_type const& cc (column_count (c)); + size_t update_columns ( + cc.total - cc.id - cc.inverse - cc.readonly - cc.separate_update); + // Statement names. // os << "static const char persist_statement_name[];"; @@ -51,7 +54,7 @@ namespace relational if (poly && !poly_derived) os << "static const char find_discriminator_statement_name[];"; - if (cc.total != cc.id + cc.inverse + cc.readonly) + if (update_columns != 0) os << "static const char update_statement_name[];"; os << "static const char erase_statement_name[];"; @@ -76,7 +79,7 @@ namespace relational { os << "static const unsigned int find_statement_types[];"; - if (cc.total != cc.id + cc.inverse + cc.readonly) + if (update_columns != 0) os << "static const unsigned int update_statement_types[];"; if (optimistic != 0) @@ -125,8 +128,7 @@ namespace relational // Container statement types. // - os << "static const unsigned int select_types[];" - << "static const unsigned int insert_types[];"; + os << "static const unsigned int insert_types[];"; if (smart) os << "static const unsigned int update_types[];" @@ -137,6 +139,44 @@ namespace relational }; entry container_traits_; + struct section_traits: relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + section_public_extra_post (user_section& s) + { + semantics::class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + + if (!poly && (abstract (c_) || + s.special == user_section::special_version)) + return; + + bool load (s.total != 0 && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + // Statement names. + // + if (load || load_opt) + os << "static const char select_name[];" + << endl; + + if (update || update_opt) + os << "static const char update_name[];" + << endl; + + // Statement types. + // + if (update || update_opt) + os << "static const unsigned int update_types[];"; + } + }; + entry section_traits_; + struct image_member: relational::image_member, member_base { image_member (base const& x) diff --git a/odb/relational/pgsql/source.cxx b/odb/relational/pgsql/source.cxx index ceda512..d5fe8fd 100644 --- a/odb/relational/pgsql/source.cxx +++ b/odb/relational/pgsql/source.cxx @@ -98,11 +98,28 @@ namespace relational struct statement_oids: object_columns_base, context { - statement_oids (statement_kind sk, bool first = true) - : object_columns_base (first), sk_ (sk) + statement_oids (statement_kind sk, + bool first = true, + object_section* section = 0) + : object_columns_base (first, column_prefix (), section), sk_ (sk) { } + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section for + // SELECT statements. + // + return section_ == 0 || + *section_ == s || + (sk_ == statement_select && + *section_ == main_section && + !s.separate_load ()); + } + virtual void traverse_pointer (semantics::data_member& m, semantics::class_& c) { @@ -265,6 +282,17 @@ namespace relational if (container (mi)) return false; + if (section_ != 0 && *section_ != section (mi.m)) + return false; + + if (var_override_.empty ()) + { + // Ignore separately loaded members. + // + if (section_ == 0 && separate_load (mi.m)) + return false; + } + // Ignore polymorphic id references; they are not returned by // the select statement. // @@ -641,8 +669,12 @@ namespace relational semantics::data_member* id (id_member (c)); semantics::data_member* optimistic (context::optimistic (c)); + column_count_type const& cc (column_count (c)); + size_t update_columns ( + cc.total - cc.id - cc.inverse - cc.readonly - cc.separate_update); + string const& n (class_fq_name (c)); string const& fn (flat_name (n)); string traits ("access::object_traits_impl< " + n + ", id_pgsql >"); @@ -684,7 +716,7 @@ namespace relational strlit (fn + "_find_discriminator") << ";" << endl; - if (cc.total != cc.id + cc.inverse + cc.readonly) + if (update_columns != 0) os << "const char " << traits << "::" << endl << "update_statement_name[] = " << strlit (fn + "_update") << ";" @@ -745,7 +777,7 @@ namespace relational << "find_statement_types[] =" << "{"; - statement_oids st (statement_select); + statement_oids st (statement_select, true); st.traverse (*id); os << "};"; @@ -753,19 +785,21 @@ namespace relational // update_statement_types. // - if (id != 0 && cc.total != cc.id + cc.inverse + cc.readonly) + if (id != 0 && update_columns != 0) { os << "const unsigned int " << traits << "::" << endl << "update_statement_types[] =" << "{"; { - statement_oids st (statement_update); + statement_oids st (statement_update, true, &main_section); st.traverse (c); } + // Not the same as update_columns. + // bool first (cc.total == cc.id + cc.inverse + cc.readonly + - cc.optimistic_managed); + cc.separate_update + cc.optimistic_managed); statement_oids st (statement_where, first); st.traverse (*id); @@ -791,11 +825,13 @@ namespace relational } virtual void - container_cache_extra_args (bool used) + extra_statement_cache_extra_args (bool c, bool s) { + bool u (c || s); + os << "," << endl - << db << "::native_binding&" << (used ? " idn" : "") << "," << endl - << "const unsigned int*" << (used ? " idt" : ""); + << db << "::native_binding&" << (u ? " idn" : "") << "," << endl + << "const unsigned int*" << (u ? " idt" : ""); } virtual void @@ -916,33 +952,6 @@ namespace relational semantics::type& vt (container_vt (t)); semantics::type& idt (container_idt (m)); - // select statement types. - // - { - os << "const unsigned int " << scope << "::" << endl - << "select_types[] =" - << "{"; - - statement_oids so (statement_where); - - if (inv) - { - // many(i)-to-many - // - if (container (*inv_m)) - so.traverse (*inv_m, idt, "value", "value"); - - // many(i)-to-one - // - else - so.traverse (*inv_m); - } - else - so.traverse (m, idt, "id", "object_id"); - - os << "};"; - } - // insert statement types. // { @@ -1065,6 +1074,74 @@ namespace relational }; entry container_traits_; + struct section_traits : relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + section_extra (user_section& s) + { + semantics::class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + + if (!poly && (abstract (c_) || + s.special == user_section::special_version)) + return; + + semantics::data_member* opt (optimistic (c_)); + + bool load (s.total != 0 && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + string name (public_name (*s.member)); + string scope (scope_ + "::" + name + "_traits"); + + // Statment names. + // + + // Prefix object name to avoid conflicts with inherited member + // statement names. + // + string fn (flat_name (class_fq_name (c_) + "_" + name)); + + if (load || load_opt) + os << "const char " << scope << "::" << endl + << "select_name[] = " << strlit (fn + "_select") << ";" + << endl; + + if (update || update_opt) + os << "const char " << scope << "::" << endl + << "update_name[] = " << strlit (fn + "_update") << ";" + << endl; + + // Statement types. + // + if (update || update_opt) + { + os << "const unsigned int " << scope << "::" << endl + << "update_types[] =" + << "{"; + + { + statement_oids st (statement_update, true, &s); + st.traverse (c_); + } + + statement_oids st (statement_where, !update); + st.traverse (*id_member (c_)); + + if (s.optimistic ()) // Note: not update_opt. + st.traverse (*opt); + + os << "};"; + } + } + }; + entry section_traits_; + struct container_cache_init_members: relational::container_cache_init_members { @@ -1078,6 +1155,18 @@ namespace relational }; entry container_cache_init_members_; + struct section_cache_init_members: + relational::section_cache_init_members + { + section_cache_init_members (base const& x): base (x) {} + + virtual void + extra_members () + { + os << ", idn, idt"; + } + }; + entry section_cache_init_members_; } } } diff --git a/odb/relational/source.cxx b/odb/relational/source.cxx index 8d7c913..ba5a464 100644 --- a/odb/relational/source.cxx +++ b/odb/relational/source.cxx @@ -2,6 +2,8 @@ // copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file +#include + #include #include @@ -52,6 +54,36 @@ traverse_object (type& c) db.string () + " >"); column_count_type const& cc (column_count (c)); + user_sections& uss (c.get ("user-sections")); + user_sections* buss (poly_base != 0 + ? &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; @@ -67,32 +99,77 @@ traverse_object (type& c) if (options.generate_query ()) query_columns_type_->traverse (c); + // Statement cache (definition). // - // Containers (abstract and concrete). - // - bool containers (has_a (c, test_container)); - bool straight_containers (false); - bool straight_readwrite_containers (false); - bool smart_containers (false); - - if (containers) + if (!reuse_abst && id != 0) { - size_t scn (has_a (c, test_straight_container)); + os << "struct " << traits << "::extra_statement_cache_type" + << "{"; - if (scn != 0) - { - straight_containers = true; + instance cm; + cm->traverse (c); + + if (containers) + os << endl; - // Inverse containers cannot be marked readonly. + bool sections (false); + instance sm; + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip the special version update section in reuse inheritance (we + // always treat it as abstract). // - straight_readwrite_containers = scn > has_a (c, test_readonly_container); + if (i->special == user_section::special_version && !poly) + continue; - // Inverse containers cannot be smart. + // Generate an entry for a readonly section in optimistic + // polymorphic root since it can be overridden and we may + // need to update the version. // - smart_containers = has_a (c, test_smart_container); + if (!i->empty () || (poly && i->optimistic ())) + { + sm->traverse (*i); + sections = true; + } } + + if (sections) + os << endl; + + os << "extra_statement_cache_type (" << endl + << db << "::connection&" << (containers || sections ? " c" : "") << + "," << endl + << "image_type&" << (sections ? " im" : "") << "," << endl + << db << "::binding&" << (containers || sections ? " id" : "") << + "," << endl + << db << "::binding&" << (sections ? " idv" : ""); + + extra_statement_cache_extra_args (containers, sections); + + os << ")"; + + instance cim; + cim->traverse (c); + + instance sim (!containers); + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + if (i->special == user_section::special_version && !poly) + continue; + + if (!i->empty () || (poly && i->optimistic ())) + sim->traverse (*i); + } + + os << "{" + << "}" + << "};"; } + // + // Containers (abstract and concrete). + // + if (containers) { instance t (c); @@ -100,6 +177,16 @@ traverse_object (type& c) } // + // Sections (abstract and concrete). + // + + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + instance t (c); + t->traverse (*i); + } + + // // Functions (abstract and concrete). // @@ -207,7 +294,7 @@ traverse_object (type& c) << "bind (" << bind_vector << " b," << endl; // If we are a derived type in a polymorphic hierarchy, then - // we get the the external id binding. + // we get the external id binding. // if (poly_derived) os << "const " << bind_vector << " id," << endl @@ -238,7 +325,7 @@ traverse_object (type& c) << "{" << "if (id != 0)" << endl << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" - << "n += id_size;" + << "n += id_size;" // Not in if for "id unchanged" optimization. << "}"; } else @@ -257,7 +344,7 @@ traverse_object (type& c) << "{" << "if (id != 0)" << endl << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" - << "n += id_size;" + << "n += id_size;" // Not in if for "id unchanged" optimization. << "}"; // Bind the image chain for the select statement. Seeing that @@ -412,32 +499,9 @@ traverse_object (type& c) // Containers (concrete). // - // Statement cache (definition). // - if (id != 0) - { - os << "struct " << traits << "::container_statement_cache_type" - << "{"; - - instance cm; - cm->traverse (c); - - os << (containers ? "\n" : "") - << "container_statement_cache_type (" << endl - << db << "::connection&" << (containers ? " c" : "") << "," << endl - << db << "::binding&" << (containers ? " id" : ""); - - container_cache_extra_args (containers); - - os << ")"; - - instance im; - im->traverse (c); - - os << "{" - << "}" - << "};"; - } + // Sections (concrete). + // // Polymorphic map. // @@ -448,6 +512,73 @@ traverse_object (type& c) << traits << "::map;" << endl; + // Sections. Do we have any new sections or overrides? + // + bool sections ( + uss.count (user_sections::count_new | + user_sections::count_override | + user_sections::count_load | + user_sections::count_update | + user_sections::count_special_version | + (poly ? user_sections::count_optimistic : 0)) != 0); + if (sections) + { + map m; + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + m[i->index] = &*i; + + os << "static const "<< traits << "::" << (abst ? "abstract_" : "") << + "info_type::section_functions" << endl + << "section_table_for_" << flat_name (type) << "[] =" + << "{"; + + for (size_t i (0), n (uss.count (user_sections::count_total | + user_sections::count_all)); + i != n; ++i) + { + os << (i != 0 ? "," : "") << "{"; + + map::iterator j (m.find (i)); + + if (j != m.end ()) + { + user_section& s (*j->second); + string n (public_name (*s.member)); + + // load + // + if (s.load_empty ()) + os << "0"; + else + os << "&odb::section_load_impl< " << type << ", id_" << db << + ", " << traits << "::" << n << "_traits >"; + + os << "," << endl; + + // update + // + // Generate an entry for a readonly section in optimistic + // polymorphic root since it can be overridden and we may + // need to update the version. + // + if (s.update_empty () && !(poly && s.optimistic ())) + os << "0"; + else + os << "&odb::section_update_impl< " << type << ", id_" << db << + ", " << traits << "::" << n << "_traits >"; + } + else + os << "0," << endl + << "0"; + + os << "}"; + } + + os << "};"; + } + + // + // os << "const " << traits << "::" << (abst ? "abstract_" : "") << "info_type" << endl << traits << "::info (" << endl @@ -455,18 +586,21 @@ traverse_object (type& c) if (poly_derived) os << "&object_traits_impl< " << class_fq_name (*poly_base) << - ", id_" << db << " >::info"; + ", id_" << db << " >::info," << endl; else - os << "0"; + os << "0," << endl; - string n; + // Sections. + // + if (sections) + os << "section_table_for_" << flat_name (type); + else + os << "0"; if (!abst) { - n = class_fq_name (c); - os << "," << endl - << strlit (string (n, 2, string::npos)) << "," << endl + << strlit (string (type, 2, string::npos)) << "," << endl << "&odb::create_impl< " << type << " >," << endl << "&odb::dispatch_impl< " << type << ", id_" << db << " >," << endl; @@ -481,13 +615,15 @@ traverse_object (type& c) if (!abst) os << "static const " << traits << "::entry_type" << endl - << "polymorphic_entry_for_" << flat_name (n) << ";" + << "polymorphic_entry_for_" << flat_name (type) << ";" << endl; } // // Statements. // + size_t update_columns ( + cc.total - cc.id - cc.inverse - cc.readonly - cc.separate_update); qname table (table_name (c)); string qtable (quote_id (table)); @@ -562,7 +698,8 @@ traverse_object (type& c) statement_columns sc; { statement_kind sk (statement_select); // Imperfect forwarding. - instance t (qtable, sk, sc, d); + object_section* s (&main_section); // Imperfect forwarding. + instance t (qtable, sk, sc, d, s); t->traverse (c); process_statement_columns (sc, statement_select); find_column_counts[poly_depth - d] = sc.size (); @@ -588,9 +725,14 @@ traverse_object (type& c) j->traverse (polymorphic_base (c)); } - bool f (false); // @@ (im)perfect forwarding - instance j (c, f, d); // @@ (im)perfect forwarding - j->traverse (c); + { + // For the find statement, don't join section members. + // + bool f (false); // @@ (im)perfect forwarding + object_section* s (&main_section); // @@ (im)perfect forwarding + instance j (c, f, d, s); + j->traverse (c); + } instance qp (table); for (object_columns_list::iterator b (id_cols->begin ()), i (b); @@ -690,7 +832,7 @@ traverse_object (type& c) // update_statement // - if (cc.total != cc.id + cc.inverse + cc.readonly) + if (update_columns != 0) { instance qp (table); @@ -698,7 +840,8 @@ traverse_object (type& c) { query_parameters* p (qp.get ()); // Imperfect forwarding. statement_kind sk (statement_update); // Imperfect forwarding. - instance t (sk, sc, p); + object_section* s (&main_section); // Imperfect forwarding. + instance t (sk, sc, p, s); t->traverse (c); process_statement_columns (sc, statement_update); } @@ -713,6 +856,18 @@ traverse_object (type& c) os << strlit (c + (++i != e ? "," : "")) << endl; } + // This didn't work out: cannot change the identity column. + // + //if (sc.empty ()) + //{ + // // We can end up with nothing to set if we need to "touch" a row + // // in order to increment its optimistic concurrency version. In + // // this case just do a dummy assignment based on the id column. + // // + // string const& c (quote_id (id_cols->begin ()->name)); + // os << strlit (c + "=" + c) << endl; + //} + update_statement_extra (c); for (object_columns_list::iterator b (id_cols->begin ()), i (b); @@ -792,8 +947,9 @@ traverse_object (type& c) // statement_columns sc; { - statement_kind sk (statement_select); // Imperfect forwarding. - instance oc (qtable, sk, sc, poly_depth); + statement_kind sk (statement_select); //@@ Imperfect forwarding. + object_section* s (&main_section); //@@ Imperfect forwarding. + instance oc (qtable, sk, sc, poly_depth, s); oc->traverse (c); process_statement_columns (sc, statement_select); } @@ -819,8 +975,11 @@ traverse_object (type& c) if (id != 0) { + // For the query statement we also join section members so that they + // can be used in the WHERE clause. + // bool t (true); //@@ (im)perfect forwarding - instance oj (c, t, poly_depth); //@@ (im)perfect forwarding + instance oj (c, t, poly_depth); oj->traverse (c); } @@ -1009,7 +1168,7 @@ traverse_object (type& c) // If we don't have auto id, then obj is a const reference. // string obj (auto_id ? "obj" : "const_cast< object_type& > (obj)"); - string init (optimimistic_version_init (*opt)); + string init (optimistic_version_init (*opt)); if (!opt_ma_set->synthesized) os << "// From " << location_string (opt_ma_set->loc, true) << endl; @@ -1062,8 +1221,10 @@ traverse_object (type& c) << "{" << "bind (idb.bind, i);" << "sts.id_image_version (i.version);" - << "idb.version++;" - << "}"; + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; if (poly && !straight_containers && !abst) os << "}"; @@ -1071,10 +1232,39 @@ traverse_object (type& c) if (straight_containers) { + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + instance t (container_calls::persist_call); t->traverse (c); } + // Reset sections: loaded, unchanged. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + // Skip overridden sections; they have been reset by the base. + // + if (i->base != 0 && poly_derived) + continue; + + data_member& m (*i->member); + + // Section access is always by reference. + // + member_access& ma (m.get ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + } + // Call callback (post_persist). // if (!abst) // If we are poly-abstract, then top will always be false. @@ -1093,6 +1283,17 @@ traverse_object (type& c) // if (id != 0 && (!readonly || poly)) { + // See if we have any sections that we might have to update. + // + bool sections (false); + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // This test will automatically skip the special version update section. + // + if (!i->update_empty () && i->update != user_section::update_manual) + sections = true; + } + os << "void " << traits << "::" << endl << "update (database& db, const object_type& obj"; @@ -1146,16 +1347,71 @@ traverse_object (type& c) << endl; } + // If we have change-updated sections that contain change-tracking + // containers, then mark such sections as changed if any of the + // containers was changed. Do this before calling the base so that + // we account for the whole hierarchy before we actually start + // updating any sections (important for optimistic concurrency + // version). + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + user_section& s (*i); + + if (s.update != user_section::update_change) + continue; + + if (!has_a (c, test_smart_container, &s)) + continue; + + data_member& m (*s.member); + + os << "// " << m.name () << endl + << "//" << endl + << "{"; + + // Section access is always by reference. + // + member_access& ma (m.get ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // VC++ cannot grok the constructor syntax. + // + os << "const odb::section& s = " << ma.translate ("obj") << ";" + << endl; + + os << "if ("; + + // Unless we are always loaded, test for being loaded. + // + if (s.load != user_section::load_eager) + os << "s.loaded () && "; + + // And for not already being changed. + // + os << "!s.changed ())" + << "{"; + + instance t (container_calls::section_call, &s); + t->traverse (c); + + os << "}" // if + << "}"; + } + if (poly_derived) { bool readonly_base (context::readonly (*poly_base)); if (readonly_base || - cc.total != cc.id + cc.inverse + cc.readonly || - straight_readwrite_containers) + update_columns != 0 || + update_containers || + sections) { - os << db << "::connection& conn (" << endl - << db << "::transaction::current ().connection ());" + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection ());" << "statements_type& sts (" << endl << "conn.statement_cache ().find_object ());" << endl; @@ -1171,13 +1427,11 @@ traverse_object (type& c) else { // Otherwise, we have to initialize the id image ourselves. If - // we don't have any columns or containers to update, then we - // only have to do it if this is not a top-level call. If we - // are abstract, then top is always false. + // we don't have any columns, containers or sections to update, + // then we only have to do it if this is not a top-level call. + // If we are abstract, then top is always false. // - if (cc.total == cc.id + cc.inverse + cc.readonly && - !straight_readwrite_containers && - !abst) + if (update_columns == 0 && !update_containers && !sections && !abst) os << "if (!top)"; os << "{" @@ -1195,17 +1449,16 @@ traverse_object (type& c) << "bind (idb.bind, i);" << "sts.id_image_version (i.version);" << "idb.version++;" + // Optimistic poly base cannot be readonly. << "}" << "}"; } - if (cc.total != cc.id + cc.inverse + cc.readonly) - { + if (update_columns != 0) os << "const binding& idb (sts.id_image_binding ());" << endl; - } - if (cc.total != cc.id + cc.inverse + cc.readonly) + if (update_columns != 0) { // Initialize the object image. // @@ -1255,16 +1508,23 @@ traverse_object (type& c) // to update. // } - else if (cc.total != cc.id + cc.inverse + cc.readonly) + else if (update_columns != 0) { - os << db << "::connection& conn (" << endl - << db << "::transaction::current ().connection ());" + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection ());" << "statements_type& sts (" << endl << "conn.statement_cache ().find_object ());" << endl; - // Initialize object and id images. + // Initialize id image. // + if (!id_ma->synthesized) + os << "// From " << location_string (id_ma->loc, true) << endl; + + os << "const id_type& id (" << endl + << id_ma->translate ("obj") << ");"; + if (opt != 0) { if (!opt_ma_get->synthesized) @@ -1274,31 +1534,30 @@ traverse_object (type& c) << opt_ma_get->translate ("obj") << ");"; } - os << "id_image_type& i (sts.id_image ());"; - - if (!id_ma->synthesized) - os << "// From " << location_string (id_ma->loc, true) << endl; - - os << "init (i, " << id_ma->translate ("obj"); + os << "id_image_type& idi (sts.id_image ());" + << "init (idi, id"; if (opt != 0) os << ", &v"; os << ");" - << endl - << "image_type& im (sts.image ());"; + << endl; + + // Initialize object image. + // + os << "image_type& im (sts.image ());"; if (generate_grow) - os << "if ("; + os << "if ("; - os << "init (im, obj, statement_update)"; + os << "init (im, obj, statement_update)"; - if (generate_grow) - os << ")" << endl - << "im.version++"; + if (generate_grow) + os << ")" << endl + << "im.version++"; - os << ";" - << endl; + os << ";" + << endl; // Update binding is bound to two images (object and id) // so we have to track both versions. @@ -1319,23 +1578,25 @@ traverse_object (type& c) // suffix of the update bind array (see object_statements). // os << "binding& idb (sts.id_image_binding ());" - << "if (i.version != sts.update_id_image_version () ||" << endl + << "if (idi.version != sts.update_id_image_version () ||" << endl << "idb.version == 0)" << "{" // If the id binding is up-to-date, then that means update // binding is too and we just need to update the versions. // - << "if (i.version != sts.id_image_version () ||" << endl + << "if (idi.version != sts.id_image_version () ||" << endl << "idb.version == 0)" << "{" - << "bind (idb.bind, i);" + << "bind (idb.bind, idi);" // Update the id binding versions since we may use them later // to update containers. // - << "sts.id_image_version (i.version);" - << "idb.version++;" - << "}" - << "sts.update_id_image_version (i.version);" + << "sts.id_image_version (idi.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}" + << "sts.update_id_image_version (idi.version);" << endl << "if (!u)" << endl << "imb.version++;" @@ -1354,11 +1615,14 @@ traverse_object (type& c) { // We don't have any columns to update. Note that we still have // to make sure this object exists in the database. For that we - // will run the SELECT query using the find_() function. - // + // will run the SELECT query using the find_() function. Note + // that this case cannot be optimistic (we always update the + // version column) and we don't need to worry about initializing + // version in id image for sections below. // - os << db << "::connection& conn (" << endl - << db << "::transaction::current ().connection ());" + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection ());" << "statements_type& sts (" << endl << "conn.statement_cache ().find_object ());" << endl; @@ -1379,7 +1643,9 @@ traverse_object (type& c) os << "discriminator_ (sts, id, 0);" << endl; - if (!abst) + // The same rationale as a couple of screens up. + // + if (!update_containers && !sections && !abst) os << "if (!top)"; os << "{" @@ -1394,6 +1660,7 @@ traverse_object (type& c) << "bind (idb.bind, i);" << "sts.id_image_version (i.version);" << "idb.version++;" + // Cannot be optimistic. << "}" << "}"; } @@ -1408,13 +1675,20 @@ traverse_object (type& c) } } - if (straight_readwrite_containers) + if (update_containers || sections) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + + if (update_containers) { - instance t (container_calls::update_call); + instance t (container_calls::update_call, + &main_section); t->traverse (c); } // Update the optimistic concurrency version in the object member. + // Do it before updating sections since they will update it as well. + // The same code as in section_traits. // if (opt != 0 && !poly_derived) { @@ -1422,7 +1696,7 @@ traverse_object (type& c) // constness. // string obj ("const_cast< object_type& > (obj)"); - string inc (optimimistic_version_increment (*opt)); + string inc (optimistic_version_increment (*opt)); if (!opt_ma_set->synthesized) os << "// From " << location_string (opt_ma_set->loc, true) << endl; @@ -1465,6 +1739,123 @@ traverse_object (type& c) } } + // Update sections that are loaded and need updating. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + if (i->update_empty () || i->update == user_section::update_manual) + continue; + + // Here is the deal: for polymorphic section overrides, we could + // have used the same approach as in load_() where the class that + // defines the section data member initiates the section update. + // However, if we used this approach, then we would get what can + // be called "interleaved" update statements. That is, a section + // update would update the section data in "derived" tables before + // updating the main section in these derived tables. While this + // does not feel right, it can also cause more real problems if, + // for example, there are constraints or some such. Generally, + // we always want to update the main section data first for each + // table in the hierarchy. + // + // So what we are going to do instead is call section traits + // update at each override point (and indicate to it not to + // call base). One tricky aspect is who is going to reset the + // changed state. We have to do it at the top since otherwise + // overrides won't know the section has changed. Finding out + // whether we are the final override (in section terms, not + // object terms) is tricky. + // + data_member& m (*i->member); + + // Section access is always by reference. + // + member_access& ma (m.get ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << "if ("; + + // Unless we are always loaded, test for being loaded. + // + string sep; + if (i->load != user_section::load_eager) + { + os << ma.translate ("obj") << ".loaded ()"; + sep = " && "; + } + + // If change-updated section, check that it has changed. + // + if (i->update == user_section::update_change) + os << sep << ma.translate ("obj") << ".changed ()"; + + os << ")" + << "{"; + + // Re-initialize the id+ver image with new version which may + // have changed. Here we take a bit of a shortcut and not + // re-bind the image since we know only version may have + // changed and that is always an integer (cannot grow). + // + if (opt != 0) + { + if (!opt_ma_get->synthesized) + os << "// From " << location_string (opt_ma_get->loc, true) << endl; + + os << "const version_type& v (" << endl + << opt_ma_get->translate ("obj") << ");"; + + if (!id_ma->synthesized) + os << "// From " << location_string (id_ma->loc, true) << endl; + + os << "init (sts.id_image (), " << id_ma->translate ("obj") << + ", &v);" + << endl; + } + + os << public_name (m) << "_traits::update (esc, obj"; + + if (poly_derived && i->base != 0) + { + // Normally we don't want to call base (base object's update() + // would have called it; see comment above). There is one + // exception, however, and that is a readonly base section + // in optimistic class. In this case, base object's update() + // won't call it (it is readonly) but it needs to be called + // in order to increment the version. + // + bool base (false); + for (user_section* b (i->base); !base && b != 0; b = b->base) + { + if (b->total != b->inverse + b->readonly || + b->readwrite_containers) + break; // We have a readwrite base. + else if (b->optimistic ()) + base = true; // We have a readonly optimistic root. + } + + os << ", " << (base ? "true" : "false"); + } + + os << ");"; + + // Clear the change flag and arm the transaction rollback callback. + // + if (i->update == user_section::update_change) + { + // If polymorphic, do it only if we are the final override. + // + if (poly) + os << "if (root_traits::map->find (typeid (obj))." << + "final_section_update (info, " << i->index << "UL))" << endl; + + os << ma.translate ("obj") << ".reset (true, false, &tr);"; + } + + os << "}"; + } + // Call callback (post_update). // if (!abst) // If we are poly-abstract, then top will always be false. @@ -1558,8 +1949,10 @@ traverse_object (type& c) << "{" << "bind (idb.bind, i);" << "sts.id_image_version (i.version);" - << "idb.version++;" - << "}"; + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; if (poly) os << "}"; @@ -1572,6 +1965,9 @@ traverse_object (type& c) // if (straight_containers) { + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + instance t (container_calls::erase_id_call); t->traverse (c); } @@ -1654,7 +2050,9 @@ traverse_object (type& c) << "conn.statement_cache ().find_object ());"; if (poly_derived) - os << "root_statements_type& rsts (sts.root_statements ());"; + os << endl + << "root_statements_type& rsts (sts.root_statements ());" + << "ODB_POTENTIALLY_UNUSED (rsts);"; os << endl; @@ -1702,6 +2100,7 @@ traverse_object (type& c) << "bind (idb.bind, i);" << rsts << ".id_image_version (i.version);" << "idb.version++;" + // Cannot be optimistic. << "}"; if (poly) @@ -1713,8 +2112,16 @@ traverse_object (type& c) // here since in case of a custom schema, it might not be // there). // - instance t (container_calls::erase_obj_call); - t->traverse (c); + + if (straight_containers) + { + os << "extra_statement_cache_type& esc (" << + "sts.extra_statement_cache ());" + << endl; + + instance t (container_calls::erase_obj_call); + t->traverse (c); + } os << "if (sts.erase_statement ().execute () != 1)" << endl << "throw object_not_persistent ();" @@ -1741,32 +2148,14 @@ traverse_object (type& c) << "init (i, id, &v);" << endl; - // To update the id part of the optimistic id binding we have - // to do it indirectly via the id binding, since both id and - // optimistic id bindings just point to the suffix of the - // update bind array (see object_statements). - // - os << "binding& oidb (" << rsts << ".optimistic_id_image_binding ());" - << "if (i.version != " << rsts << - ".optimistic_id_image_version () ||" << endl - << "oidb.version == 0)" - << "{" - // If the id binding is up-to-date, then that means optimistic - // id binding is too and we just need to update the versions. - // - << "binding& idb (" << rsts << ".id_image_binding ());" + os << "binding& idb (" << rsts << ".id_image_binding ());" << "if (i.version != " << rsts << ".id_image_version () ||" << endl << "idb.version == 0)" << "{" << "bind (idb.bind, i);" - // Update the id binding versions since we may use them later - // to delete containers. - // << rsts << ".id_image_version (i.version);" << "idb.version++;" - << "}" - << rsts << ".optimistic_id_image_version (i.version);" - << "oidb.version++;" + << rsts << ".optimistic_id_image_binding ().version++;" << "}"; if (poly) @@ -1841,6 +2230,10 @@ traverse_object (type& c) // if (straight_containers) { + os << "extra_statement_cache_type& esc (" << + "sts.extra_statement_cache ());" + << endl; + instance t (container_calls::erase_obj_call); t->traverse (c); } @@ -1868,6 +2261,10 @@ traverse_object (type& c) os << "if (top)" << "{"; + // Note that we don't reset sections since the object is now + // transient and the state of a section in a transient object + // is undefined. + // Remove from the object cache. // os << "pointer_cache_traits::erase (db, id);"; @@ -1880,10 +2277,6 @@ traverse_object (type& c) os << "}"; } } - else if (smart_containers) - { - - } else { os << "callback (db, obj, callback_event::pre_erase);" @@ -2272,7 +2665,7 @@ traverse_object (type& c) if (delay_freeing_statement_result) os << "ar.free ();"; - os << "load_ (sts, obj);" + os << "load_ (sts, obj, true);" << rsts << ".load_delayed ();" << "l.unlock ();" << "callback (db, obj, callback_event::post_load);" @@ -2282,6 +2675,307 @@ traverse_object (type& c) os << "}"; } + // load (section) [non-thunk version] + // + if (uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + { + os << "bool " << traits << "::" << endl + << "load (connection& conn, object_type& obj, section& s" << + (poly ? ", const info_type* pi" : "") << ")" + << "{" + << "using namespace " << db << ";" + << endl; + + if (poly) + { + // Resolve type information if we are doing indirect calls. + // + os << "if (pi == 0)" // Top-level call. + << "{" + << "const std::type_info& t (typeid (obj));"; + + if (!abst) + os << endl + << "if (t == info.type)" << endl + << "pi = &info;" + << "else" << endl; + + os << "pi = &root_traits::map->find (t);" + << "}"; + } + + // If our poly-base has load sections, then call the base version + // first. Besides checking for sections, it will also initialize + // the id image. + // + if (poly_derived && + buss->count (user_sections::count_total | + user_sections::count_load | + user_sections::count_load_empty) != 0) + { + os << "if (base_traits::load (conn, obj, s, pi))" << endl + << "return true;" + << endl; + } + else + { + // Resolve extra statements if we are doing direct calls. + // + os << db << "::connection& c (static_cast<" << db << + "::connection&> (conn));" + << "statements_type& sts (c.statement_cache ()." << + "find_object ());"; + + if (!poly) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"; + + os << endl; + + // Initialize id image. This is not necessarily the root of the + // polymorphic hierarchy. + // + os << "id_image_type& i (sts.id_image ());"; + + if (!id_ma->synthesized) + os << "// From " << location_string (id_ma->loc, true) << endl; + + os << "init (i, " << id_ma->translate ("obj") << ");" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + } + + // Dispatch. + // + bool e (false); + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + if (i->load == user_section::load_eager) + continue; + + // Overridden sections are handled by the base. + // + if (poly_derived && i->base != 0) + continue; + + data_member& m (*i->member); + + // Section access is always by reference. + // + member_access& ma (m.get ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + if (e) + os << "else "; + else + e = true; + + os << "if (&s == &" << ma.translate ("obj") << ")" << endl; + + if (!poly) + os << public_name (m) << "_traits::load (esc, obj);"; + else + { + // If this is an empty section, then there may not be any + // overrides. + // + os << "{" + << "info_type::section_load sl (" << + "pi->find_section_load (" << i->index << "UL));"; + + if (i->load_empty ()) + os << "if (sl != 0)" << endl; + + os << "sl (conn, obj, true);" + << "}"; + } + } + + os << "else" << endl + << "return false;" + << endl + << "return true;" + << "}"; + } + + // update (section) [non-thunk version] + // + if (uss.count (user_sections::count_new | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + { + os << "bool " << traits << "::" << endl + << "update (connection& conn, const object_type& obj, " << + "const section& s" << (poly ? ", const info_type* pi" : "") << ")" + << "{" + << "using namespace " << db << ";" + << endl; + + if (poly) + { + // Resolve type information if we are doing indirect calls. + // + os << "if (pi == 0)" // Top-level call. + << "{" + << "const std::type_info& t (typeid (obj));"; + + if (!abst) + os << endl + << "if (t == info.type)" << endl + << "pi = &info;" + << "else" << endl; + + os << "pi = &root_traits::map->find (t);" + << "}"; + } + + // If our poly-base has update sections, then call the base version + // first. Besides checking for sections, it will also initialize the + // id image. + // + if (poly_derived && + buss->count (user_sections::count_total | + user_sections::count_update | + user_sections::count_update_empty) != 0) + { + os << "if (base_traits::update (conn, obj, s, pi))" << endl + << "return true;" + << endl; + } + else + { + // Resolve extra statements if we are doing direct calls. + // + os << db << "::connection& c (static_cast<" << db << + "::connection&> (conn));" + << "statements_type& sts (c.statement_cache ()." << + "find_object ());"; + + if (!poly) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"; + + os << endl; + + // Initialize id image. This is not necessarily the root of the + // polymorphic hierarchy. + // + if (opt != 0) + { + if (!opt_ma_get->synthesized) + os << "// From " << location_string (opt_ma_get->loc, true) << endl; + + os << "const version_type& v (" << endl + << opt_ma_get->translate ("obj") << ");"; + } + + os << "id_image_type& i (sts.id_image ());"; + + if (!id_ma->synthesized) + os << "// From " << location_string (id_ma->loc, true) << endl; + + os << "init (i, " << id_ma->translate ("obj") << + (opt != 0 ? ", &v" : "") << ");" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + } + + // Dispatch. + // + bool e (false); + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + // Overridden sections are handled by the base. + // + if (poly_derived && i->base != 0) + continue; + + // Skip read-only sections. Polymorphic sections are always + // (potentially) read-write. + // + if (!poly && i->update_empty ()) + continue; + + data_member& m (*i->member); + + // Section access is always by reference. + // + member_access& ma (m.get ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + if (e) + os << "else "; + else + e = true; + + os << "if (&s == &" << ma.translate ("obj") << ")"; + + if (!poly) + os << public_name (m) << "_traits::update (esc, obj);"; + else + { + // If this is a readonly section, then there may not be any + // overrides. + // + os << "{" + << "info_type::section_update su (" << + "pi->find_section_update (" << i->index << "UL));"; + + if (i->update_empty ()) + { + // For optimistic section, also check that we are not the + // final override, since otherwise we will increment the + // version without updating anything. + // + if (i->optimistic ()) + os << "if (su != 0 && su != info.sections[" << i->index << + "UL].update)" << endl; + else + os << "if (su != 0)" << endl; + } + + os << "su (conn, obj);" + << "}"; + } + } + + os << "else" << endl + << "return false;" + << endl + << "return true;" + << "}"; + } + // find_ () // if (id != 0) @@ -2319,8 +3013,10 @@ traverse_object (type& c) << "{" << "bind (idb.bind, i);" << "sts.id_image_version (i.version);" - << "idb.version++;" - << "}"; + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; if (poly_derived && !abst) os << "}"; @@ -2411,38 +3107,113 @@ traverse_object (type& c) os << "}"; } - // load_() containers + // load_() + // + // Load containers, reset/reload sections. // - if (containers || poly_derived) + if (poly_derived || + load_containers || + uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) { os << "void " << traits << "::" << endl << "load_ ("; if (poly && !poly_derived) - os << "base_statements_type& sts, "; + os << "base_statements_type& sts," << endl; else - os << "statements_type& sts, "; + os << "statements_type& sts," << endl; - os << "object_type& obj"; + os << "object_type& obj," << endl + << "bool reload"; if (poly_derived) - os << ", std::size_t d"; + os << "," << endl + << "std::size_t d"; os << ")" - << "{"; + << "{" + << "ODB_POTENTIALLY_UNUSED (reload);" + << endl; if (poly_derived) os << "if (--d != 0)" << endl - << "base_traits::load_ (sts.base_statements (), obj" << + << "base_traits::load_ (sts.base_statements (), obj, reload" << (poly_base != poly_root ? ", d" : "") << ");" << endl; - if (containers) + if (load_containers || + (!poly && uss.count (user_sections::count_new | + user_sections::count_load) != 0)) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + + if (load_containers) { - instance t (container_calls::load_call); + instance t (container_calls::load_call, &main_section); t->traverse (c); } + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + // Skip overridden sections; they are handled by the base. + // + if (i->base != 0 && poly_derived) + continue; + + data_member& m (*i->member); + + // Section access is always by reference. + // + member_access& ma (m.get ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + if (i->load == user_section::load_eager) + { + // Mark an eager section as loaded. + // + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + continue; + } + + os << "if (reload)" + << "{" + // Reload sections that have been loaded, clear change state. + // + << "if (" << ma.translate ("obj") << ".loaded ())" + << "{"; + + if (!poly) + os << public_name (m) << "_traits::load (esc, obj);"; + else + { + os << "info_type::section_load sl (" << endl + << "root_traits::map->find (typeid (obj)).find_section_load (" << + i->index << "UL));"; + + if (i->load_empty ()) + os << "if (sl != 0)" << endl; + + os << "sl (sts.connection (), obj, true);"; + } + + os << ma.translate ("obj") << ".reset (true, false);" + << "}" + << "}" + << "else" << endl + // Reset to unloaded, unchanged state. + << ma.translate ("obj") << ".reset ();" + << endl; + } + os << "}"; } @@ -2494,7 +3265,7 @@ traverse_object (type& c) if (empty_depth != 0) os << "}"; - os << "load_ (sts, obj, d);" + os << "load_ (sts, obj, false, d);" << "}"; } @@ -2959,6 +3730,20 @@ traverse_object (type& c) os << "," << endl << "&" << traits << "::erase"; + + // Sections. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "," << endl + << "&" << traits << "::load"; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "," << endl + << "&" << traits << "::update"; } if (options.generate_query ()) diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx index d27110e..9dbb4e9 100644 --- a/odb/relational/source.hxx +++ b/odb/relational/source.hxx @@ -79,8 +79,9 @@ namespace relational object_columns (statement_kind sk, statement_columns& sc, - query_parameters* param = 0) - : object_columns_base (true, true), + query_parameters* param = 0, + object_section* section = 0) + : object_columns_base (true, true, section), sk_ (sk), sc_ (sc), param_ (param), depth_ (1) { } @@ -88,8 +89,9 @@ namespace relational object_columns (std::string const& table_qname, statement_kind sk, statement_columns& sc, - size_t depth = 1) - : object_columns_base (true, true), + size_t depth = 1, + object_section* section = 0) + : object_columns_base (true, true, section), sk_ (sk), sc_ (sc), param_ (0), @@ -98,6 +100,24 @@ namespace relational { } + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section for + // SELECT statements. Also include optimistic version into + // section's SELECT and UPDATE statements. + // + return section_ == 0 || + *section_ == s || + (sk_ == statement_select && + *section_ == main_section && + !s.separate_load ()) || + (version (mp) && + (sk_ == statement_update || sk_ == statement_select)); + } + virtual void traverse_object (semantics::class_& c) { @@ -477,56 +497,101 @@ namespace relational : object_columns_base (true, true), obj_ (obj), depth_ (depth), + section_ (0), alias_ (alias), prefix_ (prefix), suffix_ (suffix) { + init (); + } + + polymorphic_object_joins (semantics::class_& obj, + size_t depth, + user_section& section) + : object_columns_base (true, true), + obj_ (obj), + depth_ (depth), + section_ (§ion), + suffix_ ("\n") + { + init (); + } + + void + init () + { // Get the table and id columns. // table_ = alias_.empty () - ? table_qname (obj) - : quote_id (alias_ + "_" + table_name (obj).uname ()); + ? table_qname (obj_) + : quote_id (alias_ + "_" + table_name (obj_).uname ()); - cols_->traverse (*id_member (obj)); + cols_->traverse (*id_member (obj_)); } virtual void traverse_object (semantics::class_& c) { - std::ostringstream cond; + // If section is specified, skip bases that don't add anything + // to load. + // + bool skip (false), stop (false); + if (section_ != 0) + { + skip = true; + + if (section_->object == &c) + { + user_section& s (*section_); - qname table (table_name (c)); - string alias (alias_.empty () - ? quote_id (table) - : quote_id (alias_ + "_" + table.uname ())); + if (s.total != 0 || s.optimistic ()) + skip = false; - for (object_columns_list::iterator b (cols_->begin ()), i (b); - i != cols_->end (); - ++i) - { - if (i != b) - cond << " AND "; + section_ = s.base; // Move to the next base. - string qn (quote_id (i->name)); - cond << alias << '.' << qn << '=' << table_ << '.' << qn; + if (section_ == 0) + stop = true; // Stop at this base if there are no more overrides. + } } - string line (" LEFT JOIN " + quote_id (table)); + if (!skip) + { + std::ostringstream cond; + + qname table (table_name (c)); + string alias (alias_.empty () + ? quote_id (table) + : quote_id (alias_ + "_" + table.uname ())); + + for (object_columns_list::iterator b (cols_->begin ()), i (b); + i != cols_->end (); + ++i) + { + if (i != b) + cond << " AND "; + + string qn (quote_id (i->name)); + cond << alias << '.' << qn << '=' << table_ << '.' << qn; + } + + string line (" LEFT JOIN " + quote_id (table)); - if (!alias_.empty ()) - line += (need_alias_as ? " AS " : " ") + alias; + if (!alias_.empty ()) + line += (need_alias_as ? " AS " : " ") + alias; - line += " ON " + cond.str (); + line += " ON " + cond.str (); - os << prefix_ << strlit (line) << suffix_; + os << prefix_ << strlit (line) << suffix_; + } - if (--depth_ != 0) + if (!stop && --depth_ != 0) inherits (c); } private: semantics::class_& obj_; size_t depth_; + user_section* section_; string alias_; string prefix_; string suffix_; @@ -540,8 +605,11 @@ namespace relational //@@ context::{cur,top}_object; might have to be created every time. // - object_joins (semantics::class_& scope, bool query, size_t depth = 1) - : object_columns_base (true, true), + object_joins (semantics::class_& scope, + bool query, + size_t depth, + object_section* section = 0) + : object_columns_base (true, true, section), query_ (query), depth_ (depth), table_ (table_qname (scope)), @@ -550,6 +618,18 @@ namespace relational id_cols_->traverse (id_); } + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section. + // + return section_ == 0 || + *section_ == s || + (*section_ == main_section && !s.separate_load ()); + } + virtual void traverse_object (semantics::class_& c) { @@ -816,9 +896,12 @@ namespace relational { typedef bind_member base; + // NULL section means we are generating object bind(). + // bind_member (string const& var = string (), - string const& arg = string ()) - : member_base (var, 0, string (), string ()), + string const& arg = string (), + object_section* section = 0) + : member_base (var, 0, string (), string (), section), arg_override_ (arg) { } @@ -855,6 +938,11 @@ namespace relational if (container (mi)) return false; + // Treat version as present in every section. + // + if (section_ != 0 && !version (mi.m) && *section_ != section (mi.m)) + return false; + // Ignore polymorphic id references; they are bound in a special // way. // @@ -869,12 +957,20 @@ namespace relational if (var_override_.empty ()) { + if (section_ == 0 && separate_load (mi.m) && inverse (mi.m)) + return false; + os << "// " << mi.m.name () << endl << "//" << endl; + // Order of these tests is important. + // if (!insert_send_auto_id && id (mi.m) && auto_ (mi.m)) os << "if (sk != statement_insert && sk != statement_update)" << "{"; + else if (section_ == 0 && separate_load (mi.m)) + os << "if (sk == statement_insert)" + << "{"; else if (inverse (mi.m, key_prefix_) || version (mi.m)) os << "if (sk == statement_select)" << "{"; @@ -887,7 +983,8 @@ namespace relational if (id (mi.m) || readonly (mi.m) || - ((c = composite (mi.t)) && readonly (*c))) + ((c = composite (mi.t)) && readonly (*c)) || + (section_ == 0 && separate_update (mi.m))) os << "if (sk != statement_update)" << "{"; } @@ -946,6 +1043,8 @@ namespace relational // if (!insert_send_auto_id && id (mi.m) && auto_ (mi.m)) block = true; + else if (section_ == 0 && separate_load (mi.m)) + block = true; else if (inverse (mi.m, key_prefix_) || version (mi.m)) block = true; else if (!readonly (*context::top_object)) @@ -954,7 +1053,8 @@ namespace relational if (id (mi.m) || readonly (mi.m) || - ((c = composite (mi.t)) && readonly (*c))) + ((c = composite (mi.t)) && readonly (*c)) || + (section_ == 0 && separate_update (mi.m))) block = true; } @@ -1014,42 +1114,33 @@ namespace relational column_count_type const& cc (column_count (c)); - os << "n += " << cc.total << "UL"; + os << "n += "; - // select = total - // insert = total - inverse - optimistic_managed - // update = total - inverse - optimistic_managed - id - readonly + // select = total - separate_load + // insert = total - inverse - optimistic_managed - id(auto & !sending) + // update = total - inverse - optimistic_managed - id - readonly - + // separate_update // - if (cc.inverse != 0 || - cc.optimistic_managed != 0 || - (!ro && (cc.id != 0 || cc.readonly != 0))) - { - os << " - (" << endl - << "sk == statement_select ? 0 : "; - - if (cc.inverse != 0 || cc.optimistic_managed != 0) - os << (cc.inverse + cc.optimistic_managed) << "UL"; - - if (!ro && (cc.id != 0 || cc.readonly != 0)) - { - if (cc.inverse != 0 || cc.optimistic_managed != 0) - os << " + "; - - os << "(" << endl - << "sk == statement_insert ? "; - - if (insert_send_auto_id || !auto_ (*id_member (c))) - os << "0"; - else - os << cc.id << "UL"; - - os << " : " << cc.id + cc.readonly << "UL)"; - } - - os << ")"; - } - - os << ";"; + size_t select (cc.total - cc.separate_load); + size_t insert (cc.total - cc.inverse - cc.optimistic_managed); + size_t update (insert - cc.id - cc.readonly - cc.separate_update); + + semantics::data_member* id; + if (!insert_send_auto_id && (id = id_member (c)) != 0 && auto_ (*id)) + insert -= cc.id; + + if (select == insert && insert == update) + os << select << "UL;"; + else if (select != insert && insert == update) + os << "sk == statement_select ? " << select << "UL : " << + insert << "UL;"; + else if (select == insert && insert != update) + os << "sk == statement_update ? " << update << "UL : " << + select << "UL;"; + else + os << "sk == statement_select ? " << select << "UL : " << + "sk == statement_insert ? " << insert << "UL : " << + update << "UL;"; if (check) os << "}"; @@ -1066,8 +1157,11 @@ namespace relational { typedef grow_member base; - grow_member (size_t& index, string const& var = string ()) - : member_base (var, 0, string (), string ()), index_ (index) + grow_member (size_t& index, + string const& var = string (), + user_section* section = 0) + : member_base (var, 0, string (), string (), section), + index_ (index) { } @@ -1131,8 +1225,9 @@ namespace relational typedef init_image_member base; init_image_member (string const& var = string (), - string const& member = string ()) - : member_base (var, 0, string (), string ()), + string const& member = string (), + user_section* section = 0) + : member_base (var, 0, string (), string (), section), member_override_ (member) { } @@ -1182,6 +1277,9 @@ namespace relational if (container (mi) || inverse (mi.m, key_prefix_)) return false; + if (section_ != 0 && *section_ != section (mi.m)) + return false; + // Ignore polymorphic id references; they are initialized in a // special way. // @@ -1221,8 +1319,15 @@ namespace relational if (id (mi.m) || readonly (mi.m) || + (section_ == 0 && separate_update (mi.m)) || ((c = composite (mi.t)) && readonly (*c))) // Can't be id. - os << "if (sk == statement_insert)"; + { + // If we are generating section init(), then sk can only be + // statement_update. + // + if (section_ == 0) + os << "if (sk == statement_insert)"; + } } os << "{"; @@ -1478,8 +1583,9 @@ namespace relational init_value_member (string const& member = string (), string const& var = string (), - bool ignore_implicit_discriminator = true) - : member_base (var, 0, string (), string ()), + bool ignore_implicit_discriminator = true, + user_section* section = 0) + : member_base (var, 0, string (), string (), section), member_override_ (member), ignore_implicit_discriminator_ (ignore_implicit_discriminator) { @@ -1529,6 +1635,9 @@ namespace relational if (container (mi)) return false; + if (section_ != 0 && *section_ != section (mi.m)) + return false; + // Ignore polymorphic id references; they are initialized in a // special way. // @@ -1549,6 +1658,11 @@ namespace relational } else { + // Ignore separately loaded members. + // + if (section_ == 0 && separate_load (mi.m)) + return false; + os << "// " << mi.m.name () << endl << "//" << endl << "{"; @@ -2335,7 +2449,7 @@ namespace relational << "//" << endl << "if (id != 0)" << endl << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" - << "n += id_size;" + << "n += id_size;" // Not in if for "id unchanged" optimization. << endl; // We don't need to update the bind index since this is the @@ -2403,7 +2517,7 @@ namespace relational << "//" << endl << "if (id != 0)" << endl << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" - << "n += id_size;" + << "n += id_size;" // Not in if for "id unchanged" optimization. << endl; switch (ck) @@ -2491,7 +2605,7 @@ namespace relational << "//" << endl << "if (id != 0)" << endl << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" - << "n += id_size;" + << "n += id_size;" // Not in if for "id unchanged" optimization. << endl; // We don't need to update the bind index since this is the @@ -3256,7 +3370,7 @@ namespace relational semantics::class_& c_; }; - // Container statement cache members. + // Extra statement cache members for containers. // struct container_cache_members: object_members_base, virtual context { @@ -3316,6 +3430,54 @@ namespace relational bool first_; }; + // Extra statement cache members for sections. + // + struct section_cache_members: virtual context + { + typedef section_cache_members base; + + virtual void + traverse (user_section& s) + { + string traits (public_name (*s.member) + "_traits"); + + os << db << "::" << "section_statements< " << + class_fq_name (*s.object) << ", " << traits << " > " << + s.member->name () << ";"; + } + }; + + struct section_cache_init_members: virtual context + { + typedef section_cache_init_members base; + + section_cache_init_members (bool first): first_ (first) {} + + virtual void + traverse (user_section& s) + { + if (first_) + { + os << endl + << ": "; + first_ = false; + } + else + os << "," << endl + << " "; + + os << s.member->name () << " (c, im, id, idv"; + extra_members (); + os << ")"; + } + + virtual void + extra_members () {} + + protected: + bool first_; + }; + // Calls for container members. // struct container_calls: object_members_base, virtual context @@ -3328,17 +3490,33 @@ namespace relational load_call, update_call, erase_obj_call, - erase_id_call + erase_id_call, + section_call }; - container_calls (call_type call) - : object_members_base (true, false, true), + container_calls (call_type call, object_section* section = 0) + : object_members_base (true, false, true, false, section), call_ (call), obj_prefix_ ("obj"), modifier_ (0) { } + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section for + // load calls. + // + return section_ == 0 || + *section_ == s || + (call_ == load_call && + *section_ == main_section && + !s.separate_load ()); + } + virtual void traverse_composite_wrapper (semantics::data_member* m, semantics::class_& c, @@ -3428,6 +3606,7 @@ namespace relational // In certain cases we don't need to do anything. // if ((call_ != load_call && inverse) || + (call_ == section_call && !smart) || (call_ == update_call && readonly (member_path_, member_scope_))) return; @@ -3522,21 +3701,21 @@ namespace relational { os << traits << "::persist (" << endl << var << "," << endl - << "sts.container_statment_cache ()." << sts_name << ");"; + << "esc." << sts_name << ");"; break; } case load_call: { os << traits << "::load (" << endl << var << "," << endl - << "sts.container_statment_cache ()." << sts_name << ");"; + << "esc." << sts_name << ");"; break; } case update_call: { os << traits << "::update (" << endl << var << "," << endl - << "sts.container_statment_cache ()." << sts_name << ");"; + << "esc." << sts_name << ");"; break; } case erase_obj_call: @@ -3546,7 +3725,7 @@ namespace relational if (smart) os << "&" << var << "," << endl; - os << "sts.container_statment_cache ()." << sts_name << ");" + os << "esc." << sts_name << ");" << endl; break; } @@ -3557,10 +3736,17 @@ namespace relational if (smart) os << "0," << endl; - os << "sts.container_statment_cache ()." << sts_name << ");" + os << "esc." << sts_name << ");" << endl; break; } + case section_call: + { + os << "if (" << traits << "::container_traits_type::changed (" << + var << "))" << endl + << "s.reset (true, true);"; // loaded, changed + break; + } } if (call_ != erase_id_call && (call_ != erase_obj_call || smart)) @@ -3592,155 +3778,1093 @@ namespace relational member_access* modifier_; }; - // Output a list of parameters for the persist statement. // - struct persist_statement_params: object_columns_base, virtual context + // + struct section_traits: traversal::class_, virtual context { - typedef persist_statement_params base; + typedef section_traits base; - persist_statement_params (string& params, query_parameters& qp) - : params_ (params), qp_ (qp) + section_traits (semantics::class_& c) + : c_ (c), + scope_ ("access::object_traits_impl< " + class_fq_name (c) + + ", id_" + db.string () + " >") { } + // Additional code that need to be executed following the call to + // init_value(). + // virtual void - traverse_pointer (semantics::data_member& m, semantics::class_& c) + init_value_extra () { - if (!inverse (m, key_prefix_)) - object_columns_base::traverse_pointer (m, c); } - virtual bool - traverse_column (semantics::data_member& m, string const&, bool first) + virtual void + process_statement_columns (statement_columns&, statement_kind) { - string p; - - if (version (m)) - p = version_value (m); - else if (context::id (m) && auto_ (m)) // Only simple id can be auto. - p = qp_.auto_id (); - else - p = qp_.next (); - - if (!p.empty ()) - { - if (!first) - params_ += ','; - - params_ += (p != "DEFAULT" ? convert_to (p, column_type (), m) : p); - } - - return !p.empty (); } - virtual string - version_value (semantics::data_member&) + virtual void + section_extra (user_section&) { - return "1"; } - private: - string& params_; - query_parameters& qp_; - }; - - // - // - struct class_: traversal::class_, virtual context - { - typedef class_ base; - - class_ () - : query_columns_type_ (false, false, false), - view_query_columns_type_ (false), - grow_base_ (index_), - grow_member_ (index_), - grow_version_member_ (index_, "version_"), - grow_discriminator_member_ (index_, "discriminator_"), - bind_id_member_ ("id_"), - bind_version_member_ ("version_"), - bind_discriminator_member_ ("discriminator_"), - init_id_image_member_ ("id_", "id"), - init_version_image_member_ ("version_", "(*v)"), - init_id_value_member_ ("id"), - init_version_value_member_ ("v"), - init_named_version_value_member_ ("v", "version_"), - init_discriminator_value_member_ ("d", "", false), - init_named_discriminator_value_member_ ( - "d", "discriminator_", false) + // Returning "1" means increment by one. + // + virtual string + optimistic_version_increment (semantics::data_member&) { - init (); + return "1"; } - class_ (class_ const&) - : root_context (), //@@ -Wextra - context (), - query_columns_type_ (false, false, false), - view_query_columns_type_ (false), - grow_base_ (index_), - grow_member_ (index_), - grow_version_member_ (index_, "version_"), - grow_discriminator_member_ (index_, "discriminator_"), - bind_id_member_ ("id_"), - bind_version_member_ ("version_"), - bind_discriminator_member_ ("discriminator_"), - init_id_image_member_ ("id_", "id"), - init_version_image_member_ ("version_", "(*v)"), - init_id_value_member_ ("id"), - init_version_value_member_ ("v"), - init_named_version_value_member_ ("v", "version_"), - init_discriminator_value_member_ ("d", "", false), - init_named_discriminator_value_member_ ( - "d", "discriminator_", false) + virtual void + update_statement_extra (user_section&) { - init (); } - void - init () + virtual void + traverse (user_section& s) { - if (generate_grow) - { - grow_base_inherits_ >> grow_base_; - grow_member_names_ >> grow_member_; - } + using semantics::class_; + using semantics::data_member; - bind_base_inherits_ >> bind_base_; - bind_member_names_ >> bind_member_; + data_member& m (*s.member); - init_image_base_inherits_ >> init_image_base_; - init_image_member_names_ >> init_image_member_; + class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c_); + class_* poly_base (poly_derived ? &polymorphic_base (c_) : 0); - init_value_base_inherits_ >> init_value_base_; - init_value_member_names_ >> init_value_member_; - } + data_member* opt (optimistic (c_)); - virtual void - init_auto_id (semantics::data_member&, // id member - string const&) // image variable prefix - { - if (insert_send_auto_id) - assert (false); - } + // Treat the special version update sections as abstract in reuse + // inheritance. + // + bool reuse_abst (!poly && + (abstract (c_) || + s.special == user_section::special_version)); - virtual void - init_image_pre (type&) - { - } + bool load (s.total != 0 && s.separate_load ()); + bool load_con (s.containers && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); - virtual void - init_value_extra () - { - } + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_con (s.readwrite_containers); + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); - virtual void - traverse (type& c) - { - if (!options.at_once () && class_file (c) != unit.file ()) + // Don't generate anything for empty sections. + // + if (!(load || load_con || load_opt || + update || update_con || update_opt)) return; - context::top_object = context::cur_object = &c; - + // If we are adding a new section to a derived class in an optimistic + // polymorphic hierarchy, then pretend it inherits from the special + // version update section. + // + user_section* rs (0); + if (opt != 0) + { + // Skip overrides and get to the new section if polymorphic. + // + for (rs = &s; poly && rs->base != 0; rs = rs->base) ; + + if (rs != 0) + { + if (rs->object != &opt->scope ()) + rs->base = &(poly ? poly_root : &opt->scope ())-> + get ("user-sections").back (); + else + rs = 0; + } + } + + string name (public_name (m) + "_traits"); + string scope (scope_ + "::" + name); + + os << "// " << m.name () << endl + << "//" << endl + << endl; + + // bind (id, image_type) + // + if (load || load_opt || update || update_opt) + { + os << "std::size_t " << scope << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "const " << bind_vector << (reuse_abst ? "," : " id,") << endl + << "std::size_t" << (reuse_abst ? "," : " id_size,") << endl + << "image_type& i," << endl + << db << "::statement_kind sk)" + << "{" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl + << "using namespace " << db << ";" + << endl + << "std::size_t n (0);" + << endl; + + // Bind reuse base. It is always first and we never ask it + // to bind id(+ver). + // + if (s.base != 0 && !poly_derived) + { + user_section& b (*s.base); + + bool load (b.total != 0 && b.separate_load ()); + bool load_opt (b.optimistic () && b.separate_load ()); + + bool update (b.total != b.inverse + b.readonly); + + if (load || load_opt || update) + os << "// " << class_name (*b.object) << endl + << "//" << endl + << "n += object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::bind (" << endl + << "b, 0, 0, i, sk);" + << endl; + } + + // Bind members. + // + { + instance bm ("", "", &s); + traversal::names n (*bm); + names (c_, n); + } + + // Bind polymorphic image chain for the select statement. + // + if (s.base != 0 && poly_derived && s.separate_load ()) + { + // Find the next base that has something to load, if any. + // + user_section* b (s.base); + string acc (".base"); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != 0 || b->optimistic ()) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + if (b != 0) + os << "// " << class_name (*b->object) << endl + << "//" << endl + << "if (sk == statement_select)" << endl + << "n += object_traits_impl< " << class_fq_name (*b->object) << + ", id_" << db << " >::" << public_name (*b->member) << + "_traits::bind (" << endl + << "b + n, 0, 0, *i" << acc << ", sk);" + << endl; + } + + if (!reuse_abst) + os << "// object_id" << endl + << "//" << endl + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << endl; + + os << "return n;" + << "}"; + } + + // grow () + // + if (generate_grow && (load || load_opt)) + { + os << "bool " << scope << "::" << endl + << "grow (image_type& i, " << truncated_vector << " t)" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (t);" + << endl + << "bool grew (false);" + << endl; + + size_t index (0); + + if (s.base != 0 && !poly_derived) + { + user_section& b (*s.base); + + bool load (b.total != 0); + bool load_opt (b.optimistic ()); + + if (load || load_opt) + { + os << "// " << class_name (*b.object) << endl + << "//" << endl + << "grew = object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::grow (i, t);" + << endl; + + index += b.total + (load_opt ? 1 : 0); + } + } + + { + user_section* ps (&s); + instance gm (index, "", ps); + traversal::names n (*gm); + names (c_, n); + } + + // Grow polymorphic image chain. + // + if (s.base != 0 && poly_derived) + { + // Find the next base that has something to load, if any. + // + user_section* b (s.base); + string acc (".base"); + size_t cols; + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + cols = b->total + (b->optimistic () ? 1 : 0); + if (cols != 0) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + if (b != 0) + os << "// " << class_name (*b->object) << endl + << "//" << endl + << "if (object_traits_impl< " << class_fq_name (*b->object) << + ", id_" << db << " >::" << public_name (*b->member) << + "_traits::grow (" << endl + << "*i" << acc << ", t + " << cols << "UL))" << endl + << "i" << acc << "->version++;" + << endl; + } + + os << "return grew;" << endl + << "}"; + } + + // init (object, image) + // + if (load) + { + os << "void " << scope << "::" << endl + << "init (object_type& o, const image_type& i, database* db)" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);" + << endl; + + if (s.base != 0) + { + if (!poly_derived) + { + user_section& b (*s.base); + + bool load (b.total != 0); + + if (load) + os << "// " << class_name (*b.object) << endl + << "//" << endl + << "object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::init (o, i, db);" + << endl; + } + else + { + // Find the next base that has something to load, if any. + // + user_section* b (s.base); + string acc (".base"); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != 0) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + if (b != 0) + os << "// " << class_name (*b->object) << endl + << "//" << endl + << "object_traits_impl< " << class_fq_name (*b->object) << + ", id_" << db << " >::" << public_name (*b->member) << + "_traits::init (" << endl + << "o, *i" << acc << ", db);" + << endl; + } + } + + { + instance iv ("", "", true, &s); + traversal::names n (*iv); + names (c_, n); + } + + os << "}"; + } + + // init (image, object) + // + if (update) + { + os << (generate_grow ? "bool " : "void ") << scope << "::" << endl + << "init (image_type& i, const object_type& o)" + << "{" + << "using namespace " << db << ";" + << endl + << "statement_kind sk (statement_insert);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl; + + // There is no call to init_image_pre() here (which calls the + // copy callback for some databases) since we are not going to + // touch any of the members that were loaded by query. + + if (generate_grow) + os << "bool grew (false);" + << endl; + + if (s.base != 0 && !poly_derived) + { + user_section& b (*s.base); + + bool update (b.total != b.inverse + b.readonly); + + if (update) + os << "// " << class_name (*b.object) << endl + << "//" << endl + << (generate_grow ? "grew = " : "") << + "object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::init (i, o);" + << endl; + } + + { + instance ii ("", "", &s); + traversal::names n (*ii); + names (c_, n); + } + + if (generate_grow) + os << "return grew;"; + + os << "}"; + } + + // The rest does not apply to reuse-abstract sections. + // + if (reuse_abst) + { + section_extra (s); + return; + } + + // Statements. + // + qname table (table_name (c_)); + string qtable (quote_id (table)); + + instance id_cols; + id_cols->traverse (*id_member (c_)); + + // select_statement + // + if (load || load_opt) + { + size_t depth (poly_derived ? polymorphic_depth (c_) : 1); + + statement_columns sc; + { + statement_kind sk (statement_select); // Imperfect forwarding. + object_section* ps (&s); // Imperfect forwarding. + instance t (qtable, sk, sc, depth, ps); + t->traverse (c_); + process_statement_columns (sc, statement_select); + } + + os << "const char " << scope << "::" << endl + << "select_statement[] =" << endl + << strlit ("SELECT ") << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "")) << endl; + } + + os << strlit (" FROM " + qtable) << endl; + + // Join polymorphic bases. + // + if (depth != 1 && s.base != 0) + { + size_t d (depth - 1); //@@ (im)perfect forward. + user_section& bs (*s.base); //@@ (im)perfect forward. + instance j (c_, d, bs); + j->traverse (*poly_base); + } + + // Join tables of inverse members belonging to this section. + // + { + bool f (false); // @@ (im)perfect forwarding + object_section* ps (&s); // @@ (im)perfect forwarding + instance j (c_, f, depth, ps); + j->traverse (c_); + } + + instance qp (table); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + os << endl; + + os << strlit ((i == b ? " WHERE " : " AND ") + + qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (), i->type, *i->member)); + } + + os << ";" + << endl; + } + + // update_statement + // + if (update || update_opt) + { + instance qp (table); + + statement_columns sc; + { + query_parameters* p (qp.get ()); // Imperfect forwarding. + statement_kind sk (statement_update); // Imperfect forwarding. + object_section* ps (&s); // Imperfect forwarding. + instance t (sk, sc, p, ps); + t->traverse (c_); + process_statement_columns (sc, statement_update); + } + + os << "const char " << scope << "::" << endl + << "update_statement[] =" << endl + << strlit ("UPDATE " + qtable + " SET ") << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "")) << endl; + } + + // This didn't work out: cannot change the identity column. + // + //if (sc.empty ()) + //{ + // // We can end up with nothing to set if we need to "touch" a row + // // in order to increment its optimistic concurrency version. In + // // this case just do a dummy assignment based on the id column. + // // + // string const& c (quote_id (id_cols->begin ()->name)); + // os << strlit (c + "=" + c) << endl; + //} + + update_statement_extra (s); + + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + os << endl; + + os << strlit ((i == b ? " WHERE " : " AND ") + + quote_id (i->name) + "=" + + convert_to (qp->next (), i->type, *i->member)); + } + + if (s.optimistic ()) // Note: not update_opt. + { + os << endl + << strlit (" AND " + column_qname (*opt, column_prefix ()) + + "=" + convert_to (qp->next (), *opt)); + } + + os << ";" + << endl; + } + + // load () + // + if (load || load_opt || load_con) + { + os << "void " << scope << "::" << endl + << "load (extra_statement_cache_type& esc, object_type& obj" << + (poly ? ", bool top" : "") << ")" + << "{"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (top);" + << endl; + + // Load values, if any. + // + if (load || load_opt) + { + // The SELECT statement for the top override loads all the + // values. + // + if (poly) + os << "if (top)" + << "{"; + + // Note that we don't use delayed load machinery here. While + // a section can definitely contain self-referencing pointers, + // loading such a pointer won't mess up the data members in the + // image that we care about. It also holds true for streaming + // result, since the bindings are different. + + os << "using namespace " << db << ";" + << "using " << db << "::select_statement;" // Conflicts. + << endl + << "statements_type& sts (esc." << m.name () << ");" + << endl + << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding ());" + << endl; + + // For the polymorphic case, instead of storing an array of + // versions as we do for objects, we will add all the versions + // up and use that as a cumulative image chain version. If you + // meditate a bit on that, you will realize that it will work + // (hint: versions can only increase). + // + string ver; + string ver_decl; + + if (s.base != 0 && poly_derived) + { + ver = "imv"; + ver_decl = "std::size_t imv (im.version"; + + user_section* b (s.base); + string acc ("im.base"); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != 0 || b->optimistic ()) + ver_decl += " +\n" + acc + "->version"; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + ver_decl += ")"; + + os << ver_decl << ";" + << endl; + } + else + ver = "im.version"; + + os << "if (" << ver << " != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select);" + << "sts.select_image_version (" << ver << ");" + << "imb.version++;" + << "}"; + + // Id binding is assumed initialized and bound. + // + os << "select_statement& st (sts.select_statement ());" + << "st.execute ();" + << "auto_result ar (st);" + << "select_statement::result r (st.fetch ());" + << endl; + + os << "if (r == select_statement::no_data)" << endl + << "throw object_not_persistent ();" + << endl; + + if (grow (c_, &s)) + { + os << "if (r == select_statement::truncated)" + << "{" + << "if (grow (im, sts.select_image_truncated ()))" << endl + << "im.version++;" + << endl; + + // The same logic as above. + // + if (s.base != 0 && poly_derived) + os << ver_decl << ";" + << endl; + + os << "if (" << ver << " != sts.select_image_version ())" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select);" + << "sts.select_image_version (" << ver << ");" + << "imb.version++;" + << "st.refetch ();" + << "}" + << "}"; + } + + if (opt != 0) // Not load_opt, we do it in poly-derived as well. + { + member_access& ma (opt->get ("get")); + + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << "if ("; + + if (poly_derived) + { + os << "root_traits::version (*im.base"; + for (class_* b (poly_base); + b != poly_root; + b = &polymorphic_base (*b)) + os << "->base"; + os << ")"; + } + else + os << "version (im)"; + + os << " != " << ma.translate ("obj") << ")" << endl + << "throw object_changed ();" + << endl; + } + + if (load) + { + os << "init (obj, im, &sts.connection ().database ());"; + init_value_extra (); // Stream results, etc. + os << endl; + } + + if (poly) + os << "}"; // if (top) + } + + // Call base to load its containers, if this is an override. + // + if (poly_derived && s.base != 0) + { + user_section* b (s.base); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + // If we don't have any values of our own but out base + // does, then allow it to load them. + // + if (b->containers || + (!load && (b->total != 0 || b->optimistic ()))) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + } + + // This one is tricky: ideally we would do a direct call to + // the base's load() (which may not be our immediate base, + // BTW) but there is no easy way to resolve base's extra + // statements from ours. So, instead, we are going to go + // via the dispatch machinery which requires a connection + // rather than statements. Not the most efficient way but + // simple. + + // Find the "previous" override by starting the search from + // our base. + // + if (b != 0) + { + // Note that here we are using the base section index to + // handle the special version update base. + // + os << "info.base->find_section_load (" << b->index << "UL) (" << + "esc." << m.name () << ".connection (), obj, " << + // If we don't have any values of our own, then allow the + // base load its. + // + (load ? "false" : "top") << ");" + << endl; + } + } + + // Load our containers, if any. + // + if (s.containers) + { + instance t (container_calls::load_call, &s); + t->traverse (c_); + } + + os << "}"; + } + + // update () + // + if (update || update_opt || update_con) + { + os << "void " << scope << "::" << endl + << "update (extra_statement_cache_type& esc, " << + "const object_type& obj" << + (poly_derived && s.base != 0 ? ", bool base" : "") << ")" + << "{"; + + // Call base if this is an override. + // + if (poly_derived && s.base != 0) + { + user_section* b (s.base); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != b->inverse + b->readonly || + b->readwrite_containers || + (poly && b->optimistic ())) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + } + + // The same (tricky) logic as in load(). Note that here we are + // using the base section index to handle the special version + // update base. + // + if (b != 0) + os << "if (base)" << endl + << "info.base->find_section_update (" << b->index << + "UL) (esc." << m.name () << ".connection (), obj);" + << endl; + else + os << "ODB_POTENTIALLY_UNUSED (base);" + << endl; + } + + // Update values, if any. + // + if (update || update_opt) + { + os << "using namespace " << db << ";" + << endl + << "statements_type& sts (esc." << m.name () << ");" + << endl + << "image_type& im (sts.image ());" + << "const binding& id (sts.idv_binding ());" // id+version + << "binding& imb (sts.update_image_binding ());" + << endl; + + if (update) + { + if (generate_grow) + os << "if ("; + + os << "init (im, obj)"; + + if (generate_grow) + os << ")" << endl + << "im.version++"; + + os << ";" + << endl; + } + + os << "if (im.version != sts.update_image_version () ||" << endl + << "id.version != sts.update_id_binding_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, id.bind, id.count, im, statement_update);" + << "sts.update_image_version (im.version);" + << "sts.update_id_binding_version (id.version);" + << "imb.version++;" + << "}"; + + os << "if (sts.update_statement ().execute () == 0)" << endl; + + if (opt == 0) + os << "throw object_not_persistent ();"; + else + os << "throw object_changed ();"; + + os << endl; + } + + // Update readwrite containers if any. + // + if (s.readwrite_containers) + { + instance t (container_calls::update_call, &s); + t->traverse (c_); + } + + // Update the optimistic concurrency version in the object member. + // Very similar code to object. + // + if (s.optimistic ()) // Note: not update_opt. + { + member_access& ma_get (opt->get ("get")); + member_access& ma_set (opt->get ("set")); + + // Object is passed as const reference so we need to cast away + // constness. + // + string obj ("const_cast< object_type& > (obj)"); + string inc (optimistic_version_increment (*opt)); + + if (!ma_set.synthesized) + os << "// From " << location_string (ma_set.loc, true) << endl; + + if (ma_set.placeholder ()) + { + if (!ma_get.synthesized) + os << "// From " << location_string (ma_get.loc, true) << endl; + + if (inc == "1") + os << ma_set.translate ( + obj, ma_get.translate ("obj") + " + 1") << ";"; + else + os << ma_set.translate (obj, inc) << ";"; + } + else + { + // If this member is const and we have a synthesized direct + // access, then cast away constness. Otherwise, we assume + // that the user-provided expression handles this. + // + bool cast (ma_set.direct () && const_type (opt->type ())); + if (cast) + os << "const_cast< version_type& > (" << endl; + + os << ma_set.translate (obj); + + if (cast) + os << ")"; + + if (inc == "1") + os << "++;"; + else + os << " = " << inc << ";"; + } + } + + os << "}"; + } + + section_extra (s); + + if (rs != 0) + rs->base = 0; + } + + protected: + semantics::class_& c_; + string scope_; + }; + + // Output a list of parameters for the persist statement. + // + struct persist_statement_params: object_columns_base, virtual context + { + typedef persist_statement_params base; + + persist_statement_params (string& params, query_parameters& qp) + : params_ (params), qp_ (qp) + { + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + if (!inverse (m, key_prefix_)) + object_columns_base::traverse_pointer (m, c); + } + + virtual bool + traverse_column (semantics::data_member& m, string const&, bool first) + { + string p; + + if (version (m)) + p = version_value (m); + else if (context::id (m) && auto_ (m)) // Only simple id can be auto. + p = qp_.auto_id (); + else + p = qp_.next (); + + if (!p.empty ()) + { + if (!first) + params_ += ','; + + params_ += (p != "DEFAULT" ? convert_to (p, column_type (), m) : p); + } + + return !p.empty (); + } + + virtual string + version_value (semantics::data_member&) + { + return "1"; + } + + private: + string& params_; + query_parameters& qp_; + }; + + // + // + struct class_: traversal::class_, virtual context + { + typedef class_ base; + + class_ () + : query_columns_type_ (false, false, false), + view_query_columns_type_ (false), + grow_base_ (index_), + grow_member_ (index_), + grow_version_member_ (index_, "version_"), + grow_discriminator_member_ (index_, "discriminator_"), + bind_id_member_ ("id_"), + bind_version_member_ ("version_"), + bind_discriminator_member_ ("discriminator_"), + init_id_image_member_ ("id_", "id"), + init_version_image_member_ ("version_", "(*v)"), + init_id_value_member_ ("id"), + init_version_value_member_ ("v"), + init_named_version_value_member_ ("v", "version_"), + init_discriminator_value_member_ ("d", "", false), + init_named_discriminator_value_member_ ( + "d", "discriminator_", false) + { + init (); + } + + class_ (class_ const&) + : root_context (), //@@ -Wextra + context (), + query_columns_type_ (false, false, false), + view_query_columns_type_ (false), + grow_base_ (index_), + grow_member_ (index_), + grow_version_member_ (index_, "version_"), + grow_discriminator_member_ (index_, "discriminator_"), + bind_id_member_ ("id_"), + bind_version_member_ ("version_"), + bind_discriminator_member_ ("discriminator_"), + init_id_image_member_ ("id_", "id"), + init_version_image_member_ ("version_", "(*v)"), + init_id_value_member_ ("id"), + init_version_value_member_ ("v"), + init_named_version_value_member_ ("v", "version_"), + init_discriminator_value_member_ ("d", "", false), + init_named_discriminator_value_member_ ( + "d", "discriminator_", false) + { + init (); + } + + void + init () + { + if (generate_grow) + { + grow_base_inherits_ >> grow_base_; + grow_member_names_ >> grow_member_; + } + + bind_base_inherits_ >> bind_base_; + bind_member_names_ >> bind_member_; + + init_image_base_inherits_ >> init_image_base_; + init_image_member_names_ >> init_image_member_; + + init_value_base_inherits_ >> init_value_base_; + init_value_member_names_ >> init_value_member_; + } + + virtual void + init_auto_id (semantics::data_member&, // id member + string const&) // image variable prefix + { + if (insert_send_auto_id) + assert (false); + } + + virtual void + init_image_pre (type&) + { + } + + virtual void + init_value_extra () + { + } + + virtual void + traverse (type& c) + { + if (!options.at_once () && class_file (c) != unit.file ()) + return; + + context::top_object = context::cur_object = &c; + if (object (c)) traverse_object (c); else if (view (c)) @@ -3793,7 +4917,8 @@ namespace relational object_extra (type&) {} virtual void - container_cache_extra_args (bool /*used*/) {} + extra_statement_cache_extra_args (bool /*containers*/, + bool /*sections*/) {} virtual void object_query_statement_ctor_args (type&, @@ -3815,15 +4940,15 @@ namespace relational } virtual string - optimimistic_version_init (semantics::data_member&) + optimistic_version_init (semantics::data_member&) { return "1"; } - // Returning "1" means incremenet by one. + // Returning "1" means increment by one. // virtual string - optimimistic_version_increment (semantics::data_member&) + optimistic_version_increment (semantics::data_member&) { return "1"; } @@ -4078,6 +5203,9 @@ namespace relational if (features.view) os << "#include " << endl; + if (features.section) + os << "#include " << endl; + os << "#include " << endl << "#include " << endl; diff --git a/odb/relational/sqlite/context.cxx b/odb/relational/sqlite/context.cxx index 35ec91b..664af35 100644 --- a/odb/relational/sqlite/context.cxx +++ b/odb/relational/sqlite/context.cxx @@ -123,8 +123,8 @@ namespace relational { struct has_grow: traversal::class_ { - has_grow (bool& r) - : r_ (r) + has_grow (bool& r, user_section* s) + : r_ (r), section_ (s) { *this >> inherits_ >> *this; } @@ -137,7 +137,7 @@ namespace relational if (!(context::object (c) || context::composite (c))) return; - if (c.count ("sqlite-grow")) + if (section_ == 0 && c.count ("sqlite-grow")) r_ = c.get ("sqlite-grow"); else { @@ -148,29 +148,41 @@ namespace relational if (!r_) names (c); - c.set ("sqlite-grow", r_); + if (section_ == 0) + c.set ("sqlite-grow", r_); } } private: bool& r_; + user_section* section_; traversal::inherits inherits_; }; struct has_grow_member: member_base { has_grow_member (bool& r, + user_section* section = 0, semantics::type* type = 0, string const& key_prefix = string ()) - : relational::member_base (type, string (), key_prefix), + : relational::member_base (type, string (), key_prefix, section), r_ (r) { } + virtual bool + pre (member_info& mi) + { + return (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)); + } + virtual void traverse_composite (member_info& mi) { // By calling grow() instead of recursing, we reset any overrides. + // We also don't pass section since they don't apply inside + // composites. // r_ = r_ || context::grow (dynamic_cast (mi.t)); } @@ -187,14 +199,14 @@ namespace relational } bool context:: - grow_impl (semantics::class_& c) + grow_impl (semantics::class_& c, user_section* section) { - if (c.count ("sqlite-grow")) + if (section == 0 && c.count ("sqlite-grow")) return c.get ("sqlite-grow"); bool r (false); - has_grow ct (r); - has_grow_member mt (r); + has_grow ct (r, section); + has_grow_member mt (r, section); traversal::names names; ct >> names >> mt; ct.traverse (c); @@ -214,7 +226,7 @@ namespace relational grow_impl (semantics::data_member& m, semantics::type& t, string const& kp) { bool r (false); - has_grow_member mt (r, &t, kp); + has_grow_member mt (r, 0, &t, kp); mt.traverse (m); return r; } diff --git a/odb/relational/sqlite/context.hxx b/odb/relational/sqlite/context.hxx index a3721df..95e4ae6 100644 --- a/odb/relational/sqlite/context.hxx +++ b/odb/relational/sqlite/context.hxx @@ -67,7 +67,7 @@ namespace relational convert_expr (string const&, semantics::data_member&, bool); virtual bool - grow_impl (semantics::class_&); + grow_impl (semantics::class_&, user_section*); virtual bool grow_impl (semantics::data_member&); diff --git a/odb/validator.cxx b/odb/validator.cxx index efe9236..080aaa4 100644 --- a/odb/validator.cxx +++ b/odb/validator.cxx @@ -4,11 +4,13 @@ #include +#include #include #include #include #include +#include #include #include @@ -110,6 +112,50 @@ 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)) + { + if (m.count ("section-member")) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ": " << + "error: data member belonging to a section can only be a " << + "direct member of a persistent class" << endl; + valid_ = false; + } + + if (t.fq_name () == "::odb::section") + { + os << m.file () << ":" << m.line () << ":" << m.column () << ": " << + "error: section data member can only be a direct member of a " << + "persistent class" << endl; + valid_ = false; + } + } + + // Make sure the load and update pragmas are only specified on + // section members. + // + if (t.fq_name () != "::odb::section") + { + if (m.count ("section-load")) + { + location_t loc (m.get ("section-load-location")); + error (loc) << "'#pragma db load' can only be specified for " + "a section data member" << endl; + valid_ = false; + } + + if (m.count ("section-update")) + { + location_t loc (m.get ("section-update-location")); + error (loc) << "'#pragma db update' can only be specified for " + "a section data member" << endl; + valid_ = false; + } + } + // Resolve null overrides. // override_null (m); @@ -438,9 +484,16 @@ namespace if (id->count ("default")) { os << id->file () << ":" << id->line () << ":" << id->column () - << ": error: object id member cannot have default value" - << endl; + << ": error: object id member cannot have default value" << endl; + valid_ = false; + } + // Complain if an id member is in a section. + // + if (id->count ("section-member")) + { + os << id->file () << ":" << id->line () << ":" << id->column () + << ": error: object id member cannot be in a section" << endl; valid_ = false; } @@ -464,7 +517,7 @@ namespace // Make sure we have the class declared optimistic. // - if (&optimistic->scope () == &c && !c.count ("optimistic")) + if (&m.scope () == &c && !c.count ("optimistic")) { os << m.file () << ":" << m.line () << ":" << m.column () << ":" << " error: version data member in a class not declared " @@ -490,7 +543,7 @@ namespace // Make sure id and version members are in the same class. The // current architecture relies on that. // - if (id != 0 && &id->scope () != &optimistic->scope ()) + if (id != 0 && &id->scope () != &m.scope ()) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: object id and version members are in different " @@ -519,6 +572,15 @@ namespace valid_ = false; } + // Complain if the version member is in a section. + // + if (m.count ("section-member")) + { + os << m.file () << ":" << m.line () << ":" << m.column () + << ": error: version member cannot be in a section" << endl; + valid_ = false; + } + // This takes care of also marking derived classes as optimistic. // c.set ("optimistic-member", optimistic); @@ -571,6 +633,28 @@ namespace if (poly_root != 0) c.set ("polymorphic-root", poly_root); + // Sectionable objects. + // + if (c.count ("sectionable")) + { + if (optimistic == 0) + { + location_t l (c.get ("sectionable-location")); + error (l) << "only optimistic class can be sectionable" << endl; + valid_ = false; + } + else if (&optimistic->scope () != &c && poly_root != &c) + { + location l (c.get ("sectionable-location")); + error (l) << "only optimistic class that declares the version " << + "data member or that is a root of a polymorphic hierarchy can " << + "be sectionable" << endl; + info (optimistic->location ()) << "version member is declared " << + "here" << endl; + valid_ = false; + } + } + // Update features set based on this object. // if (options.at_once () || class_file (c) == unit.file ()) @@ -878,6 +962,56 @@ namespace virtual void traverse_object (type& c) { + bool poly (polymorphic (c)); + + // Make sure we have no empty or pointless sections unless we + // are reuse-abstract or polymorphic. + // + if (!poly && !abstract (c)) + { + user_sections& uss (c.get ("user-sections")); + + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + user_section& s (*i); + + // Skip the special version update section (we always treat it + // as abstract in reuse inheritance). + // + if (s.special == user_section::special_version) + continue; + + semantics::data_member& m (*s.member); + location const& l (m.location ()); + + if (s.total == 0 && !s.containers) + { + error (l) << "empty section" << endl; + + if (&m.scope () != &c) + info (c.location ()) << "as seen in this non-abstract " << + "persistent class" << endl; + + valid_ = false; + continue; + } + + // Eager-loaded section with readonly members. + // + if (s.load == user_section::load_eager && s.update_empty ()) + { + error (l) << "eager-loaded section with readonly members is " << + "pointless" << endl; + + if (&m.scope () != &c) + info (c.location ()) << "as seen in this non-abstract " << + "persistent class" << endl; + + valid_ = false; + } + } + } + if (semantics::data_member* id = id_member (c)) { semantics::type& t (utype (*id)); @@ -937,6 +1071,20 @@ namespace } } } + else + { + // Make sure an object without id has no sections. + // + user_sections& uss (c.get ("user-sections")); + + if (!uss.empty ()) + { + semantics::data_member& m (*uss.front ().member); + os << m.file () << ":" << m.line () << ":" << m.column () + << ": error: object without id cannot have sections" << endl; + valid_ = false; + } + } } virtual void -- cgit v1.1