aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2011-11-01 12:41:02 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2011-11-01 14:30:22 +0200
commit7871bd9b681f449cc3938750ce70fa1ed5400dcd (patch)
tree3784716722430f06799016e3b7bdae7231fd9120
parentc11ace0f4a665ac0dfb269860ef04dce284b75f5 (diff)
Implement support for optimistic concurrency
New pragmas: optimistic, version. New test: optimistic. New database function: reload().
-rw-r--r--odb/context.cxx2
-rw-r--r--odb/context.hxx30
-rw-r--r--odb/pragma.cxx42
-rw-r--r--odb/relational/header.hxx61
-rw-r--r--odb/relational/inline.hxx37
-rw-r--r--odb/relational/mysql/source.cxx12
-rw-r--r--odb/relational/oracle/source.cxx12
-rw-r--r--odb/relational/pgsql/header.cxx41
-rw-r--r--odb/relational/pgsql/source.cxx53
-rw-r--r--odb/relational/source.hxx317
-rw-r--r--odb/relational/sqlite/source.cxx12
-rw-r--r--odb/tracer/header.cxx8
-rw-r--r--odb/tracer/source.cxx14
-rw-r--r--odb/validator.cxx177
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<semantics::data_member*> ("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> image_type_;
instance<image_member> id_image_member_;
+ instance<image_member> version_image_member_;
instance<query_columns_type> query_columns_type_;
instance<query_columns_type> 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<statement_oids> st (statement_where);
- st->traverse_column (*id, "", false);
+ st->traverse_column (*id, "", first);
+ }
+
+ if (optimistic != 0)
+ {
+ instance<statement_oids> 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<statement_oids> st (statement_where);
+ st->traverse_column (*id, "", true);
+ }
+
+ {
+ instance<statement_oids> 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<object_columns> 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<query_parameters> 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<container_calls> 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<container_calls> 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<object_type> ());"
+ << 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<container_calls> 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_type> ());"
- << 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<object_type> ());"
+ << 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> bind_member_;
traversal::names bind_member_names_;
instance<bind_member> bind_id_member_;
+ instance<bind_member> bind_version_member_;
instance<init_image_base> init_image_base_;
traversal::inherits init_image_base_inherits_;
@@ -4133,12 +4373,15 @@ namespace relational
traversal::names init_image_member_names_;
instance<init_image_member> init_id_image_member_;
+ instance<init_image_member> init_version_image_member_;
instance<init_value_base> init_value_base_;
traversal::inherits init_value_base_inherits_;
instance<init_value_member> init_value_member_;
traversal::names init_value_member_names_;
+
instance<init_value_member> init_id_value_member_;
+ instance<init_value_member> 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;
}