From 7871bd9b681f449cc3938750ce70fa1ed5400dcd Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 1 Nov 2011 12:41:02 +0200 Subject: Implement support for optimistic concurrency New pragmas: optimistic, version. New test: optimistic. New database function: reload(). --- odb/context.cxx | 2 + odb/context.hxx | 30 +++- odb/pragma.cxx | 42 +++++- odb/relational/header.hxx | 61 ++++++-- odb/relational/inline.hxx | 37 ++++- odb/relational/mysql/source.cxx | 12 +- odb/relational/oracle/source.cxx | 12 +- odb/relational/pgsql/header.cxx | 41 +++-- odb/relational/pgsql/source.cxx | 53 ++++++- odb/relational/source.hxx | 317 ++++++++++++++++++++++++++++++++++----- odb/relational/sqlite/source.cxx | 12 +- odb/tracer/header.cxx | 8 +- odb/tracer/source.cxx | 14 +- odb/validator.cxx | 177 +++++++++++++++++++--- 14 files changed, 716 insertions(+), 102 deletions(-) diff --git a/odb/context.cxx b/odb/context.cxx index f1386d2..2e96ba9 100644 --- a/odb/context.cxx +++ b/odb/context.cxx @@ -1096,6 +1096,8 @@ namespace c_.inverse++; else if (context::readonly (member_path_, member_scope_)) c_.readonly++; + else if (context::version (m)) + c_.optimistic_managed++; } context::column_count_type c_; diff --git a/odb/context.hxx b/odb/context.hxx index d1a9073..10210a3 100644 --- a/odb/context.hxx +++ b/odb/context.hxx @@ -398,12 +398,32 @@ public: return c.count ("readonly"); } + // + // bool null (semantics::data_member&); bool null (semantics::data_member&, string const& key_prefix); + // Optimistic concurrency. + // + static semantics::data_member* + optimistic (semantics::class_& c) + { + // Set by the validator. + // + return c.get ("optimistic-member", 0); + } + + static bool + version (semantics::data_member& m) + { + return m.count ("version"); + } + + // + // typedef ::class_kind class_kind_type; static class_kind_type @@ -490,12 +510,20 @@ public: public: struct column_count_type { - column_count_type (): total (0), id (0), inverse (0), readonly (0) {} + column_count_type () + : total (0), + id (0), + inverse (0), + readonly (0), + optimistic_managed (0) + { + } size_t total; size_t id; size_t inverse; size_t readonly; + size_t optimistic_managed; }; static column_count_type diff --git a/odb/pragma.cxx b/odb/pragma.cxx index eee2677..585da42 100644 --- a/odb/pragma.cxx +++ b/odb/pragma.cxx @@ -316,7 +316,8 @@ check_spec_decl_type (tree d, else if (p == "auto" || p == "column" || p == "inverse" || - p == "transient") + p == "transient" || + p == "version") { if (tc != FIELD_DECL) { @@ -341,7 +342,8 @@ check_spec_decl_type (tree d, p == "abstract" || p == "callback" || p == "query" || - p == "object") + p == "object" || + p == "optimistic") { if (tc != RECORD_TYPE) { @@ -689,6 +691,18 @@ handle_pragma (cpp_reader* reader, tt = pragma_lex (&t); } + else if (p == "optimistic") + { + // optimistic + // + + // Make sure we've got the correct declaration type. + // + if (decl != 0 && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = pragma_lex (&t); + } else if (p == "callback") { // callback (name) @@ -1460,7 +1474,7 @@ handle_pragma (cpp_reader* reader, } else if (p == "readonly") { - // transient + // readonly // // Make sure we've got the correct declaration type. @@ -1482,6 +1496,18 @@ handle_pragma (cpp_reader* reader, tt = pragma_lex (&t); } + else if (p == "version") + { + // version + // + + // Make sure we've got the correct declaration type. + // + if (decl != 0 && !check_spec_decl_type (decl, decl_name, p, loc)) + return; + + tt = pragma_lex (&t); + } else { error () << "unknown db pragma " << p << endl; @@ -1629,7 +1655,8 @@ handle_pragma_qualifier (cpp_reader* reader, string const& p) p == "inverse" || p == "unordered" || p == "readonly" || - p == "transient") + p == "transient" || + p == "version") { handle_pragma (reader, p, "member", 0, ""); return; @@ -1853,6 +1880,12 @@ handle_pragma_db_transient (cpp_reader* r) } extern "C" void +handle_pragma_db_version (cpp_reader* r) +{ + handle_pragma_qualifier (r, "version"); +} + +extern "C" void handle_pragma_db (cpp_reader* r) { tree t; @@ -1915,5 +1948,6 @@ register_odb_pragmas (void*, void*) c_register_pragma_with_expansion ("db", "unordered", handle_pragma_db_unordered); c_register_pragma_with_expansion ("db", "readonly", handle_pragma_db_readonly); c_register_pragma_with_expansion ("db", "transient", handle_pragma_db_transient); + c_register_pragma_with_expansion ("db", "version", handle_pragma_db_version); */ } diff --git a/odb/relational/header.hxx b/odb/relational/header.hxx index 355ec83..39f2bbf 100644 --- a/odb/relational/header.hxx +++ b/odb/relational/header.hxx @@ -892,6 +892,7 @@ namespace relational class1 () : id_image_member_ ("id_"), + version_image_member_ ("version_"), query_columns_type_ (false), pointer_query_columns_type_ (true) { @@ -901,6 +902,7 @@ namespace relational : root_context (), //@@ -Wextra context (), id_image_member_ ("id_"), + version_image_member_ ("version_"), query_columns_type_ (false), pointer_query_columns_type_ (true) { @@ -940,6 +942,8 @@ namespace relational bool auto_id (id ? id->count ("auto") : false); bool base_id (id ? &id->scope () != &c : false); // Comes from base. + semantics::data_member* optimistic (context::optimistic (c)); + column_count_type const& cc (column_count (c)); os << "// " << c.name () << endl @@ -992,8 +996,13 @@ namespace relational { string const& base (id->scope ().fq_name ()); - os << "typedef object_traits< " << base << " >::id_type id_type;" - << endl + os << "typedef object_traits< " << base << " >::id_type id_type;"; + + if (optimistic != 0) + os << "typedef object_traits< " << base << " >::version_type " << + "version_type;"; + + os << endl << "static const bool auto_id = object_traits< " << base << " >::auto_id;" << endl @@ -1003,13 +1012,23 @@ namespace relational } else { - semantics::names* hint; - semantics::type& t (utype (*id, hint)); + { + semantics::names* hint; + semantics::type& t (utype (*id, hint)); - os << "typedef " << t.fq_name (hint) << " id_type;" - << endl; + os << "typedef " << t.fq_name (hint) << " id_type;"; + } + + if (optimistic != 0) + { + semantics::names* hint; + semantics::type& t (utype (*optimistic, hint)); + + os << "typedef " << t.fq_name (hint) << " version_type;"; + } - os << "static const bool auto_id = " << + os << endl + << "static const bool auto_id = " << (auto_id ? "true" : "false") << ";" << endl; @@ -1018,6 +1037,9 @@ namespace relational id_image_member_->traverse (*id); + if (optimistic != 0) + version_image_member_->traverse (*optimistic); + os << "std::size_t version;" << "};"; } @@ -1054,7 +1076,8 @@ namespace relational if (id != 0 || !abstract) // We want to generate a dummy void id() accessor even if this // object has no id to help us in the runtime. This way we can - // generic code that will both for both void and non-void ids. + // write generic code that will work for both void and non-void + // ids. // os << "static id_type" << endl << "id (const object_type&);" @@ -1065,6 +1088,11 @@ namespace relational << "id (const image_type&);" << endl; + if (id != 0 && optimistic != 0) + os << "static version_type" << endl + << "version (const image_type&);" + << endl; + // grow () // if (generate_grow) @@ -1108,7 +1136,8 @@ namespace relational if (id != 0) { os << "static void" << endl - << "init (id_image_type&, const id_type&);" + << "init (id_image_type&, const id_type&" << + (optimistic != 0 ? ", const version_type* = 0" : "") << ");" << endl; } @@ -1156,6 +1185,8 @@ namespace relational cc.inverse << "UL;" << "static const std::size_t readonly_column_count = " << cc.readonly << "UL;" + << "static const std::size_t managed_optimistic_column_count = " << + cc.optimistic_managed << "UL;" << endl; // Statements. @@ -1170,6 +1201,9 @@ namespace relational os << "static const char update_statement[];"; os << "static const char erase_statement[];"; + + if (optimistic != 0) + os << "static const char optimistic_erase_statement[];"; } if (options.generate_query ()) @@ -1216,6 +1250,10 @@ namespace relational << "find (database&, const id_type&, object_type&);" << endl; + os << "static bool" << endl + << "reload (database&, object_type&);" + << endl; + // update () // if (!readonly (c)) @@ -1228,6 +1266,10 @@ namespace relational os << "static void" << endl << "erase (database&, const id_type&);" << endl; + + os << "static void" << endl + << "erase (database&, const object_type&);" + << endl; } // query () @@ -1560,6 +1602,7 @@ namespace relational private: instance image_type_; instance id_image_member_; + instance version_image_member_; instance query_columns_type_; instance pointer_query_columns_type_; diff --git a/odb/relational/inline.hxx b/odb/relational/inline.hxx index 3968dce..0c37d33 100644 --- a/odb/relational/inline.hxx +++ b/odb/relational/inline.hxx @@ -109,6 +109,8 @@ namespace relational semantics::data_member* id (id_member (c)); bool base_id (id ? &id->scope () != &c : false); // Comes from base. + semantics::data_member* optimistic (context::optimistic (c)); + os << "// " << c.name () << endl << "//" << endl << endl; @@ -139,10 +141,10 @@ namespace relational if (id != 0) { + // id (image_type) + // if (options.generate_query () && base_id) { - // id (image_type) - // os << "inline" << endl << traits << "::id_type" << endl << traits << "::" << endl @@ -153,6 +155,20 @@ namespace relational << "}"; } + // version (image_type) + // + if (optimistic != 0 && base_id) + { + os << "inline" << endl + << traits << "::version_type" << endl + << traits << "::" << endl + << "version (const image_type& i)" + << "{" + << "return object_traits< " << + optimistic->scope ().fq_name () << " >::version (i);" + << "}"; + } + // bind (id_image_type) // if (base_id) @@ -170,10 +186,11 @@ namespace relational { os << "inline" << endl << "void " << traits << "::" << endl - << "init (id_image_type& i, const id_type& id)" + << "init (id_image_type& i, const id_type& id" << + (optimistic != 0 ? ", const version_type* v" : "") << ")" << "{" << "object_traits< " << id->scope ().fq_name () << - " >::init (i, id);" + " >::init (i, id" << (optimistic != 0 ? ", v" : "") << ");" << "}"; } } @@ -184,6 +201,18 @@ namespace relational if (abstract) return; + // erase (object_type) + // + if (id != 0 && optimistic == 0) + { + os << "inline" << endl + << "void " << traits << "::" << endl + << "erase (database& db, const object_type& obj)" + << "{" + << "erase (db, id (obj));" + << "}"; + } + // callback () // os << "inline" << endl diff --git a/odb/relational/mysql/source.cxx b/odb/relational/mysql/source.cxx index d49910f..6a05878 100644 --- a/odb/relational/mysql/source.cxx +++ b/odb/relational/mysql/source.cxx @@ -197,7 +197,7 @@ namespace relational os << "// " << mi.m.name () << endl << "//" << endl; - if (inverse (mi.m, key_prefix_)) + if (inverse (mi.m, key_prefix_) || version (mi.m)) os << "if (sk == statement_select)" << "{"; // If the whole class is readonly, then we will never be @@ -242,7 +242,7 @@ namespace relational << "sk == statement_select ? 0 : "; if (cc.inverse != 0) - os << cc.inverse << "UL" << endl; + os << cc.inverse << "UL"; if (!ro && cc.readonly != 0) { @@ -266,7 +266,7 @@ namespace relational // The same logic as in pre(). // - if (inverse (mi.m, key_prefix_)) + if (inverse (mi.m, key_prefix_) || version (mi.m)) block = true; else if (!readonly (*context::top_object)) { @@ -587,6 +587,12 @@ namespace relational member = member_override_; else { + // If we are generating standard init() and this member + // contains version, ignore it. + // + if (version (mi.m)) + return false; + string const& name (mi.m.name ()); member = "o." + name; diff --git a/odb/relational/oracle/source.cxx b/odb/relational/oracle/source.cxx index 62b417b..0c40bab 100644 --- a/odb/relational/oracle/source.cxx +++ b/odb/relational/oracle/source.cxx @@ -90,7 +90,7 @@ namespace relational os << "// " << mi.m.name () << endl << "//" << endl; - if (inverse (mi.m, key_prefix_)) + if (inverse (mi.m, key_prefix_) || version (mi.m)) os << "if (sk == statement_select)" << "{"; // If the whole class is readonly, then we will never be @@ -135,7 +135,7 @@ namespace relational << "sk == statement_select ? 0 : "; if (cc.inverse != 0) - os << cc.inverse << "UL" << endl; + os << cc.inverse << "UL"; if (!ro && cc.readonly != 0) { @@ -159,7 +159,7 @@ namespace relational // The same logic as in pre(). // - if (inverse (mi.m, key_prefix_)) + if (inverse (mi.m, key_prefix_) || version (mi.m)) block = true; else if (!readonly (*context::top_object)) { @@ -335,6 +335,12 @@ namespace relational member = member_override_; else { + // If we are generating standard init() and this member + // contains version, ignore it. + // + if (version (mi.m)) + return false; + string const& name (mi.m.name ()); member = "o." + name; diff --git a/odb/relational/pgsql/header.cxx b/odb/relational/pgsql/header.cxx index 474b69c..db7a939 100644 --- a/odb/relational/pgsql/header.cxx +++ b/odb/relational/pgsql/header.cxx @@ -26,17 +26,27 @@ namespace relational if (abstract (c)) return; + semantics::data_member* id (id_member (c)); + semantics::data_member* optimistic (context::optimistic (c)); + column_count_type const& cc (column_count (c)); // Statement names. // - os << "static const char persist_statement_name[];" - << "static const char find_statement_name[];"; + os << "static const char persist_statement_name[];"; + + if (id != 0) + { + os << "static const char find_statement_name[];"; - if (cc.total != cc.id + cc.inverse + cc.readonly) - os << "static const char update_statement_name[];"; + if (cc.total != cc.id + cc.inverse + cc.readonly) + os << "static const char update_statement_name[];"; - os << "static const char erase_statement_name[];"; + os << "static const char erase_statement_name[];"; + + if (optimistic != 0) + os << "static const char optimistic_erase_statement_name[];"; + } // Query statement name. // @@ -48,14 +58,23 @@ namespace relational // Statement types. // - os << "static const unsigned int persist_statement_types[];" - << "static const unsigned int find_statement_types[];"; + os << "static const unsigned int persist_statement_types[];"; - if (cc.total != cc.id + cc.inverse + cc.readonly) - os << "static const unsigned int update_statement_types[];"; + if (id != 0) + { + os << "static const unsigned int find_statement_types[];"; - os << "static const unsigned int erase_statement_types[];" - << endl; + if (cc.total != cc.id + cc.inverse + cc.readonly) + os << "static const unsigned int update_statement_types[];"; + + os << "static const unsigned int erase_statement_types[];"; + + if (optimistic != 0) + os << "static const unsigned int " << + "optimistic_erase_statement_types[];"; + } + + os << endl; } virtual void diff --git a/odb/relational/pgsql/source.cxx b/odb/relational/pgsql/source.cxx index 6bfa3d9..0ff0bf1 100644 --- a/odb/relational/pgsql/source.cxx +++ b/odb/relational/pgsql/source.cxx @@ -122,6 +122,10 @@ namespace relational sk_ == statement_update) return false; + if ((sk_ == statement_insert || sk_ == statement_update) && + version (m)) + return false; + if (!first) os << ',' << endl; @@ -164,7 +168,7 @@ namespace relational os << "// " << mi.m.name () << endl << "//" << endl; - if (inverse (mi.m, key_prefix_)) + if (inverse (mi.m, key_prefix_) || version (mi.m)) os << "if (sk == statement_select)" << "{"; // If the whole class is readonly, then we will never be @@ -209,7 +213,7 @@ namespace relational << "sk == statement_select ? 0 : "; if (cc.inverse != 0) - os << cc.inverse << "UL" << endl; + os << cc.inverse << "UL"; if (!ro && cc.readonly != 0) { @@ -233,7 +237,7 @@ namespace relational // The same logic as in pre(). // - if (inverse (mi.m, key_prefix_)) + if (inverse (mi.m, key_prefix_) || version (mi.m)) block = true; else if (!readonly (*context::top_object)) { @@ -493,6 +497,12 @@ namespace relational member = member_override_; else { + // If we are generating standard init() and this member + // contains version, ignore it. + // + if (version (mi.m)) + return false; + string const& name (mi.m.name ()); member = "o." + name; @@ -1001,6 +1011,7 @@ namespace relational return; semantics::data_member* id (id_member (c)); + semantics::data_member* optimistic (context::optimistic (c)); column_count_type const& cc (column_count (c)); string const& n (c.fq_name ()); @@ -1028,6 +1039,12 @@ namespace relational os << name_decl << endl << "erase_statement_name[] = " << strlit (fn + "_erase") << ";" << endl; + + if (optimistic != 0) + os << name_decl << endl + << "optimistic_erase_statement_name[] = " << + strlit (fn + "_optimistic_erase") << ";" + << endl; } // Query statement name. @@ -1087,9 +1104,18 @@ namespace relational st->traverse (c); } + bool first (cc.total == cc.id + cc.inverse + cc.readonly + + cc.optimistic_managed); + { instance st (statement_where); - st->traverse_column (*id, "", false); + st->traverse_column (*id, "", first); + } + + if (optimistic != 0) + { + instance st (statement_where); + st->traverse_column (*optimistic, "", false); } os << "};"; @@ -1108,6 +1134,25 @@ namespace relational os << "};"; } + + if (id != 0 && optimistic != 0) + { + os << oid_decl << endl + << "optimistic_erase_statement_types[] =" + << "{"; + + { + instance st (statement_where); + st->traverse_column (*id, "", true); + } + + { + instance st (statement_where); + st->traverse_column (*optimistic, "", false); + } + + os << "};"; + } } virtual void diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx index dcc9c23..cabb5c5 100644 --- a/odb/relational/source.hxx +++ b/odb/relational/source.hxx @@ -90,6 +90,17 @@ namespace relational line_.clear (); + // Version column (optimistic concurrency) requires special + // handling in the UPDATE statement. + // + // + if (sk_ == statement_update && version (m)) + { + string const& qname (quote_id (name)); + line_ = qname + "=" + qname + "+1"; + return true; + } + // Inverse object pointers come from a joined table. // if (im != 0) @@ -565,20 +576,22 @@ namespace relational os << "n += " << cc.total << "UL"; // select = total - // insert = total - inverse - // update = total - inverse - id - readonly + // insert = total - inverse - optimistic_managed + // update = total - inverse - optimistic_managed - id - readonly // - if (cc.inverse != 0 || (!ro && (cc.id != 0 || cc.readonly != 0))) + 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) - os << cc.inverse << "UL" << endl; + 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) + if (cc.inverse != 0 || cc.optimistic_managed != 0) os << " + "; os << "(" << endl @@ -2085,7 +2098,9 @@ namespace relational if (count_++ != 0) params_ += ','; - if (m.count ("id") && m.count ("auto")) + if (version (m)) + params_ += "1"; + else if (m.count ("id") && m.count ("auto")) params_ += qp_.auto_id (); else params_ += qp_.next (); @@ -2112,8 +2127,11 @@ namespace relational : grow_base_ (index_), grow_member_ (index_), bind_id_member_ ("id_"), + bind_version_member_ ("version_"), init_id_image_member_ ("id_", "id"), + init_version_image_member_ ("version_", "(*v)"), init_id_value_member_ ("id"), + init_version_value_member_ ("v"), stream_ (emitter_), drop_model_ (emitter_, stream_, format_embedded), drop_table_ (emitter_, stream_, format_embedded), @@ -2131,8 +2149,11 @@ namespace relational grow_base_ (index_), grow_member_ (index_), bind_id_member_ ("id_"), + bind_version_member_ ("version_"), init_id_image_member_ ("id_", "id"), + init_version_image_member_ ("version_", "(*v)"), init_id_value_member_ ("id"), + init_version_value_member_ ("v"), stream_ (emitter_), drop_model_ (emitter_, stream_, format_embedded), drop_table_ (emitter_, stream_, format_embedded), @@ -2266,13 +2287,16 @@ namespace relational bool auto_id (id ? id->count ("auto") : false); bool base_id (id ? &id->scope () != &c : false); // Comes from base. + semantics::data_member* optimistic (context::optimistic (c)); + bool grow (false); bool grow_id (false); if (generate_grow) { grow = context::grow (c); - grow_id = id ? context::grow (*id) : false; + grow_id = (id ? context::grow (*id) : false) || + (optimistic ? context::grow (*optimistic) : false); } column_count_type const& cc (column_count (c)); @@ -2345,6 +2369,18 @@ namespace relational << "}"; } + if (id != 0 && optimistic != 0 && !base_id) + { + os << traits << "::version_type" << endl + << traits << "::" << endl + << "version (const image_type& i)" + << "{" + << "version_type v;"; + init_version_value_member_->traverse (*optimistic); + os << "return v;" + << "}"; + } + // grow () // if (generate_grow) @@ -2397,7 +2433,17 @@ namespace relational << "bind (" << bind_vector << " b, id_image_type& i)" << "{" << "std::size_t n (0);"; + bind_id_member_->traverse (*id); + + if (optimistic != 0) + { + os << "n++;" //@@ composite id + << endl; + + bind_version_member_->traverse (*optimistic); + } + os << "}"; } @@ -2449,17 +2495,27 @@ namespace relational if (id != 0 && !base_id) { os << "void " << traits << "::" << endl - << "init (id_image_type& i, const id_type& id)" + << "init (id_image_type& i, const id_type& id" << + (optimistic != 0 ? ", const version_type* v" : "") << ")" << "{"; if (grow_id) - os << "bool grew (false);"; + os << "bool grew (false);" + << endl; init_id_image_member_->traverse (*id); + if (optimistic != 0) + { + // Here we rely on the fact that init_image_member + // always wraps the statements in a block. + // + os << "if (v != 0)"; + init_version_image_member_->traverse (*optimistic); + } + if (grow_id) - os << endl - << "if (grew)" << endl + os << "if (grew)" << endl << "i.version++;"; os << "}"; @@ -2564,7 +2620,13 @@ namespace relational instance t (statement_update, true, qp.get ()); t->traverse (c); - os << strlit (" WHERE " + id_col + "=" + qp->next ()) << ";" + string where (" WHERE " + id_col + "=" + qp->next ()); + + if (optimistic != 0) + where += " AND " + column_qname (*optimistic) + "=" + + qp->next (); + + os << strlit (where) << ";" << endl; } @@ -2577,6 +2639,20 @@ namespace relational << strlit (" WHERE " + id_col + "=" + qp->next ()) << ";" << endl; } + + if (optimistic != 0) + { + instance qp; + + string where (" WHERE " + id_col + "=" + qp->next ()); + where += " AND " + column_qname (*optimistic) + "=" + qp->next (); + + os << "const char " << traits << + "::optimistic_erase_statement[] =" << endl + << strlit ("DELETE FROM " + table) << endl + << strlit (where) << ";" + << endl; + } } if (options.generate_query ()) @@ -2678,6 +2754,19 @@ namespace relational << endl; } + if (optimistic != 0) + { + // Set the version in the object member. + // + if (!auto_id || const_type (optimistic->type ())) + os << "const_cast< version_type& > (" << + "obj." << optimistic->name () << ") = 1;"; + else + os << "obj." << optimistic->name () << " = 1;"; + + os << endl; + } + if (straight_containers) { // Initialize id_image and binding. @@ -2718,9 +2807,15 @@ namespace relational { // Initialize object and id images. // - os << "id_image_type& i (sts.id_image ());" - << "init (i, obj." << id->name () << ");" - << endl + os << "id_image_type& i (sts.id_image ());"; + + if (optimistic == 0) + os << "init (i, obj." << id->name () << ");"; + else + os << "init (i, obj." << id->name () << ", &obj." << + optimistic->name () << ");"; + + os << endl << "image_type& im (sts.image ());" << "if (init (im, obj, statement_update))" << endl << "im.version++;" @@ -2748,18 +2843,33 @@ namespace relational << "if (i.version != sts.update_id_image_version () || " << "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 () || " << + "idb.version == 0)" + << "{" << "bind (idb.bind, i);" - << "sts.update_id_image_version (i.version);" - << "if (!u)" << endl - << "imb.version++;" - << endl - // Update the id binding versions since we rebound it. + // 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);" + << endl + << "if (!u)" << endl + << "imb.version++;" << "}"; - os << "sts.update_statement ().execute ();"; + os << "if (sts.update_statement ().execute () == 0)" << endl; + + if (optimistic == 0) + os << "throw object_not_persistent ();"; + else + os << "throw object_changed ();"; + + os << endl; } else { @@ -2772,20 +2882,28 @@ namespace relational if (straight_readwrite_containers) os << endl - << "binding& idb (sts.id_image_binding ());"; + << "binding& idb (sts.id_image_binding ());" + << endl; } if (straight_readwrite_containers) { - os << endl; instance t (container_calls::update_call); t->traverse (c); } + if (optimistic != 0) + { + // Update version in the object member. + // + os << "const_cast< version_type& > (" << + "obj." << optimistic->name () << ")++;"; + } + os << "}"; } - // erase () + // erase (id_type) // if (id != 0) { @@ -2823,7 +2941,6 @@ namespace relational { instance t (container_calls::erase_call); t->traverse (c); - os << endl; } os << "if (sts.erase_statement ().execute () != 1)" << endl @@ -2832,6 +2949,91 @@ namespace relational os << "}"; } + // erase (object_type) + // + if (id != 0 && optimistic != 0) + { + os << "void " << traits << "::" << endl + << "erase (database&, const object_type& obj)" + << "{" + << "using namespace " << db << ";" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection ());" + << object_statements_type << "& sts (" << endl + << "conn.statement_cache ().find_object ());" + << endl; + + // Initialize id + managed column image. + // + os << "id_image_type& i (sts.id_image ());" + << "init (i, obj." << id->name () << ", &obj." << + optimistic->name () << ");" + << 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& idb (sts.id_image_binding ());" + << "binding& oidb (sts.optimistic_id_image_binding ());" + << "if (i.version != sts.optimistic_id_image_version () || " << + "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. + // + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + // Update the id binding versions since we may use them later + // to delete containers. + // + << "sts.id_image_version (i.version);" + << "idb.version++;" + << "}" + << "sts.optimistic_id_image_version (i.version);" + << "oidb.version++;" + << "}"; + + // Erase containers first so that there are no reference + // violations (we don't want to rely on ON DELETE CASCADE + // here since in case of a custom schema, it might not be + // there). + // + if (straight_containers) + { + // Things get complicated here: we don't want to trash the + // containers and then find out that the versions don't match + // and we therefore cannot delete the object. After all, there + // is no guarantee that the user will abort the transaction. + // In fact, a perfectly reasonable scenario is to reload the + // object, re-apply the changes, and commit the transaction. + // + // There doesn't seem to be anything better than first making + // sure we can delete the object, then deleting the container + // data, and then deleting the object. To check that we can + // delete the object we are going to use find_() and then + // compare the versions. A special-purpose SELECT query would + // have been more efficient but it would complicated and bloat + // things significantly. + // + os << "if (!find_ (sts, obj." << id->name () << ") ||" << endl + << "version (sts.image ()) != obj." << optimistic->name () << + ")" << endl + << "throw object_changed ();" + << endl; + + instance t (container_calls::erase_call); + t->traverse (c); + } + + os << "if (sts.optimistic_erase_statement ().execute () != 1)" << endl + << "throw object_changed ();" + << "}"; + } + // find (id) // if (id != 0 && c.default_ctor ()) @@ -2895,18 +3097,18 @@ namespace relational << db << "::transaction::current ().connection ());" << object_statements_type << "& sts (" << endl << "conn.statement_cache ().find_object ());" - << object_statements_type << "::auto_lock l (sts);" << endl - << "if (l.locked ())" - << "{" - << "if (!find_ (sts, id))" << endl + // This can only be top-level call so auto_lock must succeed. + // + << object_statements_type << "::auto_lock l (sts);" + << endl; + + os << "if (!find_ (sts, id))" << endl << "return false;" - << "}" + << endl << "reference_cache_traits< object_type >::insert_guard ig (" << endl << "reference_cache_traits< object_type >::insert (db, id, obj));" << endl - << "if (l.locked ())" - << "{" << "callback (db, obj, callback_event::pre_load);" << "init (obj, sts.image (), db);"; @@ -2916,12 +3118,49 @@ namespace relational << "sts.load_delayed ();" << "l.unlock ();" << "callback (db, obj, callback_event::post_load);" - << "}" - << "else" << endl - << "sts.delay_load (id, obj, ig.position ());" + << "ig.release ();" + << "return true;" + << "}"; + } + + // reload() + // + if (id != 0) + { + os << "bool " << traits << "::" << endl + << "reload (database& db, object_type& obj)" + << "{" + << "using namespace " << db << ";" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection ());" + << object_statements_type << "& sts (" << endl + << "conn.statement_cache ().find_object ());" + << endl + // This can only be top-level call so auto_lock must succeed. + // + << object_statements_type << "::auto_lock l (sts);" << endl; - os << "ig.release ();" + os << "if (!find_ (sts, obj." << id->name () << "))" << endl + << "return false;" + << endl; + + if (optimistic != 0) + os << "if (version (sts.image ()) == obj." << + optimistic->name () << ")" << endl + << "return true;" + << endl; + + os << "callback (db, obj, callback_event::pre_load);" + << "init (obj, sts.image (), db);"; + + init_value_extra (); + + os << "load_ (sts, obj);" + << "sts.load_delayed ();" + << "l.unlock ();" + << "callback (db, obj, callback_event::post_load);" << "return true;" << "}"; } @@ -4126,6 +4365,7 @@ namespace relational instance bind_member_; traversal::names bind_member_names_; instance bind_id_member_; + instance bind_version_member_; instance init_image_base_; traversal::inherits init_image_base_inherits_; @@ -4133,12 +4373,15 @@ namespace relational traversal::names init_image_member_names_; instance init_id_image_member_; + instance init_version_image_member_; instance init_value_base_; traversal::inherits init_value_base_inherits_; instance init_value_member_; traversal::names init_value_member_names_; + instance init_id_value_member_; + instance init_version_value_member_; schema_emitter emitter_; emitter_ostream stream_; diff --git a/odb/relational/sqlite/source.cxx b/odb/relational/sqlite/source.cxx index 0516370..041f8aa 100644 --- a/odb/relational/sqlite/source.cxx +++ b/odb/relational/sqlite/source.cxx @@ -48,7 +48,7 @@ namespace relational os << "// " << mi.m.name () << endl << "//" << endl; - if (inverse (mi.m, key_prefix_)) + if (inverse (mi.m, key_prefix_) || version (mi.m)) os << "if (sk == statement_select)" << "{"; // If the whole class is readonly, then we will never be @@ -93,7 +93,7 @@ namespace relational << "sk == statement_select ? 0 : "; if (cc.inverse != 0) - os << cc.inverse << "UL" << endl; + os << cc.inverse << "UL"; if (!ro && cc.readonly != 0) { @@ -117,7 +117,7 @@ namespace relational // The same logic as in pre(). // - if (inverse (mi.m, key_prefix_)) + if (inverse (mi.m, key_prefix_) || version (mi.m)) block = true; else if (!readonly (*context::top_object)) { @@ -295,6 +295,12 @@ namespace relational member = member_override_; else { + // If we are generating standard init() and this member + // contains version, ignore it. + // + if (version (mi.m)) + return false; + string const& name (mi.m.name ()); member = "o." + name; diff --git a/odb/tracer/header.cxx b/odb/tracer/header.cxx index 5ba6c95..f4a3a72 100644 --- a/odb/tracer/header.cxx +++ b/odb/tracer/header.cxx @@ -77,12 +77,18 @@ namespace tracer << "update (database&, const object_type&);" << endl; - // erase () + // erase (id_type) // os << "static void" << endl << "erase (database&, const id_type&);" << endl; + // erase (object_type) + // + os << "static void" << endl + << "erase (database&, const object_type&);" + << endl; + // find () // os << "static pointer_type" << endl diff --git a/odb/tracer/source.cxx b/odb/tracer/source.cxx index 8412714..5eaa5ad 100644 --- a/odb/tracer/source.cxx +++ b/odb/tracer/source.cxx @@ -67,7 +67,7 @@ namespace tracer << "throw object_not_persistent ();" << "}"; - // erase () + // erase (id_type) // os << "void " << traits << "::" << endl << "erase (database&, const id_type& id)" @@ -79,6 +79,18 @@ namespace tracer << "throw object_not_persistent ();" << "}"; + // erase (object_type) + // + os << "void " << traits << "::" << endl + << "erase (database&, const object_type& obj)" + << "{" + << "std::cout << \"delete \" << type_name () << \" id \" << " << + "obj." << id.name () << " << std::endl;" + << endl + << "if (obj." << id.name () << " == id_type ())" << endl + << "throw object_not_persistent ();" + << "}"; + // find () // os << traits << "::pointer_type" << endl diff --git a/odb/validator.cxx b/odb/validator.cxx index 7dc7ea1..8a94357 100644 --- a/odb/validator.cxx +++ b/odb/validator.cxx @@ -107,12 +107,15 @@ namespace size_t count_; }; - // Find id member. + // Find special members (id, version). // - struct id_member: traversal::class_ + struct special_members: traversal::class_ { - id_member (class_kind kind, bool& valid, semantics::data_member*& m) - : kind_ (kind), member_ (valid, m) + special_members (class_kind kind, + bool& valid, + semantics::data_member*& id, + semantics::data_member*& optimistic) + : kind_ (kind), member_ (valid, id, optimistic) { if (kind != class_view) *this >> inherits_ >> *this; @@ -161,8 +164,10 @@ namespace private: struct member: traversal::data_member { - member (bool& valid, semantics::data_member*& m) - : valid_ (valid), m_ (m) + member (bool& valid, + semantics::data_member*& id, + semantics::data_member*& optimistic) + : valid_ (valid), id_ (id), optimistic_ (optimistic) { } @@ -171,23 +176,44 @@ namespace { if (m.count ("id")) { - if (m_ != 0) + if (id_ != 0) { cerr << m.file () << ":" << m.line () << ":" << m.column () << ":" << " error: multiple object id members" << endl; - cerr << m_->file () << ":" << m_->line () << ":" << m_->column () - << ": info: previous id member declared here" << endl; + semantics::data_member& i (*id_); + + cerr << i.file () << ":" << i.line () << ":" << i.column () + << ": info: previous id member is declared here" << endl; + + valid_ = false; + } + else + id_ = &m; + } + + if (m.count ("version")) + { + if (optimistic_ != 0) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: multiple version members" << endl; + + semantics::data_member& o (*optimistic_); + + cerr << o.file () << ":" << o.line () << ":" << o.column () + << ": info: previous version member is declared here" << endl; valid_ = false; } else - m_ = &m; + optimistic_ = &m; } } bool& valid_; - semantics::data_member*& m_; + semantics::data_member*& id_; + semantics::data_member*& optimistic_; }; class_kind kind_; @@ -323,11 +349,12 @@ namespace } } - // Check id. + // Check special members. // semantics::data_member* id (0); + semantics::data_member* optimistic (0); { - id_member t (class_object, valid_, id); + special_members t (class_object, valid_, id, optimistic); t.traverse (c); } @@ -339,10 +366,10 @@ namespace if (!(c.count ("id") || context::abstract (c))) { cerr << c.file () << ":" << c.line () << ":" << c.column () << ":" - << " error: no data member designated as object id" << endl; + << " error: no data member designated as an object id" << endl; cerr << c.file () << ":" << c.line () << ":" << c.column () << ":" - << " info: use '#pragma db id' to specify object id member" + << " info: use '#pragma db id' to specify an object id member" << endl; cerr << c.file () << ":" << c.line () << ":" << c.column () << ":" @@ -382,6 +409,90 @@ namespace id->set ("not-null", true); } + if (optimistic != 0) + { + semantics::data_member& m (*optimistic); + + // Make sure we have the class declared optimistic. + // + if (&optimistic->scope () == &c && !c.count ("optimistic")) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: version data member in a class not declared " + << "optimistic" << endl; + + cerr << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: use '#pragma db optimistic' to declare this " + << "class optimistic" << endl; + + valid_ = false; + } + + // Make sure we have object id. + // + if (id == 0) + { + cerr << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: optimistic class without an object id" << endl; + + valid_ = false; + } + + // Make sure id and version members are in the same class. The + // current architecture relies on that. + // + if (id != 0 && &id->scope () != &optimistic->scope ()) + { + cerr << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: object id and version members are in different " + << "classes" << endl; + + cerr << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: object id and version members must be in the same " + << "class" << endl; + + cerr << id->file () << ":" << id->line () << ":" << id->column () + << ": info: object id member is declared here" << endl; + + cerr << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: version member is declared here" << endl; + + valid_ = false; + } + + // Make sure this class is not readonly. + // + if (c.count ("readonly")) + { + cerr << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: optimistic class cannot be readonly" << endl; + + valid_ = false; + } + + + // This takes care of also marking derived classes as optimistic. + // + c.set ("optimistic-member", optimistic); + } + else + { + // Make sure there is a version member if the class declared + // optimistic. + // + if (c.count ("optimistic")) + { + cerr << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: optimistic class without a version member" << endl; + + cerr << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " info: use '#pragma db version' to declare on of the " + << "data members as a version" << endl; + + valid_ = false; + } + } + // Check members. // member_.count_ = 0; @@ -451,20 +562,32 @@ namespace // Check id. // semantics::data_member* id (0); + semantics::data_member* optimistic (0); { - id_member t (class_view, valid_, id); + special_members t (class_view, valid_, id, optimistic); t.traverse (c); } if (id != 0) { cerr << id->file () << ":" << id->line () << ":" << id->column () - << ": error: view type data member cannot be designated as " + << ": error: view type data member cannot be designated as an " << "object id" << endl; valid_ = false; } + if (optimistic != 0) + { + semantics::data_member& o (*optimistic); + + cerr << o.file () << ":" << o.line () << ":" << o.column () + << ": error: view type data member cannot be designated as a " + << "version" << endl; + + valid_ = false; + } + // Check members. // member_.count_ = 0; @@ -517,20 +640,32 @@ namespace // Check id. // semantics::data_member* id (0); + semantics::data_member* optimistic (0); { - id_member t (class_composite, valid_, id); + special_members t (class_composite, valid_, id, optimistic); t.traverse (c); } if (id != 0) { cerr << id->file () << ":" << id->line () << ":" << id->column () - << ": error: value type data member cannot be designated as " + << ": error: value type data member cannot be designated as an " << "object id" << endl; valid_ = false; } + if (optimistic != 0) + { + semantics::data_member& o (*optimistic); + + cerr << o.file () << ":" << o.line () << ":" << o.column () + << ": error: value type data member cannot be designated as a " + << "version" << endl; + + valid_ = false; + } + // Check members. // member_.count_ = 0; @@ -574,7 +709,7 @@ namespace cerr << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" << " error: inverse object pointer member '" << member_prefix_ - << m.name () << "' in an object without id" << endl; + << m.name () << "' in an object without an object id" << endl; valid_ = false; } @@ -587,7 +722,7 @@ namespace cerr << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" << " error: container member '" << member_prefix_ << m.name () - << "' in an object without id" << endl; + << "' in an object without an object id" << endl; valid_ = false; } -- cgit v1.1