aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2014-08-14 09:37:06 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2014-11-25 06:47:21 +0200
commit55b36b8297ef9aac9e4ccc7b98f8649534ee0ac1 (patch)
treee9f0d22285d7fc9ad814b75eac9d587c6d630995
parent19ba3497c0788f02fc417f441d87c96ce23f9446 (diff)
Implement bulk database operation support for Oracle and SQL Server
-rw-r--r--doc/manual.xhtml2
-rw-r--r--odb/pragma.cxx47
-rw-r--r--odb/relational/context.cxx1
-rw-r--r--odb/relational/context.hxx1
-rw-r--r--odb/relational/header.cxx30
-rw-r--r--odb/relational/header.hxx2
-rw-r--r--odb/relational/inline.hxx15
-rw-r--r--odb/relational/mssql/context.cxx1
-rw-r--r--odb/relational/mssql/header.cxx35
-rw-r--r--odb/relational/mssql/source.cxx69
-rw-r--r--odb/relational/mysql/context.cxx1
-rw-r--r--odb/relational/oracle/context.cxx1
-rw-r--r--odb/relational/oracle/header.cxx30
-rw-r--r--odb/relational/pgsql/context.cxx1
-rw-r--r--odb/relational/processor.cxx6
-rw-r--r--odb/relational/source.cxx409
-rw-r--r--odb/relational/source.hxx11
-rw-r--r--odb/relational/sqlite/context.cxx1
-rw-r--r--odb/relational/validator.cxx60
19 files changed, 710 insertions, 13 deletions
diff --git a/doc/manual.xhtml b/doc/manual.xhtml
index 13eb954..c89cb28 100644
--- a/doc/manual.xhtml
+++ b/doc/manual.xhtml
@@ -12939,7 +12939,7 @@ main ()
{
transaction t (db.begin ());
schema_catalog::migrate (db);
- t.end ();
+ t.commit ();
}
...
diff --git a/odb/pragma.cxx b/odb/pragma.cxx
index f40258f..4d0acae 100644
--- a/odb/pragma.cxx
+++ b/odb/pragma.cxx
@@ -378,7 +378,7 @@ check_spec_decl_type (declaration const& d,
if (tc != RECORD_TYPE)
{
error (l) << "name '" << name << "' in db pragma " << p << " does "
- << "not refer to a data member or class" << endl;
+ << "not refer to a class" << endl;
return false;
}
}
@@ -458,7 +458,8 @@ check_spec_decl_type (declaration const& d,
p == "optimistic" ||
p == "polymorphic" ||
p == "definition" ||
- p == "sectionable")
+ p == "sectionable" ||
+ p == "bulk")
{
if (tc != RECORD_TYPE)
{
@@ -1479,6 +1480,48 @@ handle_pragma (cxx_lexer& l,
tt = l.next (tl, &tn);
}
+ else if (p == "bulk")
+ {
+ // bulk (batch-size)
+ //
+
+ // 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;
+ }
+
+ // base
+ //
+ if (l.next (tl, &tn) != CPP_NUMBER || TREE_CODE (tn) != INTEGER_CST)
+ {
+ error (l) << "unsigned integer expected as batch size" << endl;
+ return;
+ }
+
+ unsigned long long b (integer (tn));
+
+ if (b == 0 || b == 1)
+ {
+ error (l) << "batch size has to be greater than 1" << endl;
+ return;
+ }
+
+ val = b;
+
+ 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 == "query")
{
// query ()
diff --git a/odb/relational/context.cxx b/odb/relational/context.cxx
index b525802..3e5596e 100644
--- a/odb/relational/context.cxx
+++ b/odb/relational/context.cxx
@@ -28,6 +28,7 @@ namespace relational
insert_send_auto_id (current ().insert_send_auto_id),
delay_freeing_statement_result (current ().delay_freeing_statement_result),
need_image_clone (current ().need_image_clone),
+ generate_bulk (current ().generate_bulk),
global_index (current ().global_index),
global_fkey (current ().global_fkey),
bind_vector (data_->bind_vector_),
diff --git a/odb/relational/context.hxx b/odb/relational/context.hxx
index d0211c6..b2a4032 100644
--- a/odb/relational/context.hxx
+++ b/odb/relational/context.hxx
@@ -270,6 +270,7 @@ namespace relational
bool insert_send_auto_id;
bool delay_freeing_statement_result;
bool need_image_clone;
+ bool generate_bulk;
bool global_index;
bool global_fkey;
diff --git a/odb/relational/header.cxx b/odb/relational/header.cxx
index 392d5f1..e8cba05 100644
--- a/odb/relational/header.cxx
+++ b/odb/relational/header.cxx
@@ -208,6 +208,11 @@ traverse_object (type& c)
if (!poly_derived && id != 0)
{
+ if (auto_id)
+ os << "static id_type" << endl
+ << "id (const id_image_type&);"
+ << endl;
+
if (options.generate_query ())
os << "static id_type" << endl
<< "id (const image_type&);"
@@ -489,6 +494,12 @@ traverse_object (type& c)
os << ");"
<< endl;
+ if (c.count ("bulk-persist"))
+ os << "static void" << endl
+ << "persist (database&, " << (auto_id ? "" : "const ") <<
+ "object_type**, std::size_t, multiple_exceptions&);"
+ << endl;
+
if (id != 0)
{
// find (id)
@@ -537,6 +548,12 @@ traverse_object (type& c)
os << ");"
<< endl;
+
+ if (c.count ("bulk-update"))
+ os << "static void" << endl
+ << "update (database&, const object_type**, std::size_t, " <<
+ "multiple_exceptions&);"
+ << endl;
}
// erase ()
@@ -559,6 +576,19 @@ traverse_object (type& c)
os << ");"
<< endl;
+ if (c.count ("bulk-erase"))
+ {
+ os << "static std::size_t" << endl
+ << "erase (database&, const id_type**, std::size_t, " <<
+ "multiple_exceptions&);"
+ << endl;
+
+ os << "static void" << endl
+ << "erase (database&, const object_type**, std::size_t, " <<
+ "multiple_exceptions&);"
+ << endl;
+ }
+
// Sections.
//
// We treat all polymorphic sections as (potentially) having something
diff --git a/odb/relational/header.hxx b/odb/relational/header.hxx
index 0af9109..7a36d0e 100644
--- a/odb/relational/header.hxx
+++ b/odb/relational/header.hxx
@@ -995,6 +995,8 @@ namespace relational
os << "typedef object_traits_impl<object_type, id_" << db <<
">::image_type image_type;"
+ << "typedef object_traits_impl<object_type, id_" << db <<
+ ">::id_image_type id_image_type;"
<< endl;
section_public_extra_pre (s);
diff --git a/odb/relational/inline.hxx b/odb/relational/inline.hxx
index ed10e09..76a6763 100644
--- a/odb/relational/inline.hxx
+++ b/odb/relational/inline.hxx
@@ -217,6 +217,7 @@ namespace relational
using semantics::data_member;
data_member* id (id_member (c));
+ bool auto_id (id && auto_ (*id));
bool base_id (id && &id->scope () != &c); // Comes from base.
data_member* optimistic (context::optimistic (c));
@@ -251,6 +252,20 @@ namespace relational
{
if (!poly_derived)
{
+ // id (id_image_type)
+ //
+ if (auto_id)
+ {
+ os << "inline" << endl
+ << traits << "::id_type" << endl
+ << traits << "::" << endl
+ << "id (const id_image_type& i)"
+ << "{"
+ << "return object_traits_impl< " << class_fq_name (*base) <<
+ ", id_" << db << " >::id (i);"
+ << "}";
+ }
+
// id (image_type)
//
if (options.generate_query ())
diff --git a/odb/relational/mssql/context.cxx b/odb/relational/mssql/context.cxx
index a0037eb..e99cdaf 100644
--- a/odb/relational/mssql/context.cxx
+++ b/odb/relational/mssql/context.cxx
@@ -89,6 +89,7 @@ namespace relational
insert_send_auto_id = false;
delay_freeing_statement_result = true;
need_image_clone = true;
+ generate_bulk = true;
global_index = false;
global_fkey = true;
data_->bind_vector_ = "mssql::bind*";
diff --git a/odb/relational/mssql/header.cxx b/odb/relational/mssql/header.cxx
index f943a8a..3bc2cbd 100644
--- a/odb/relational/mssql/header.cxx
+++ b/odb/relational/mssql/header.cxx
@@ -31,6 +31,17 @@ namespace relational
if (poly_derived || (abst && !poly))
return;
+ // Bulk operations batch size.
+ //
+ {
+ unsigned long long b (c.count ("bulk")
+ ? c.get<unsigned long long> ("bulk")
+ : 1);
+
+ os << "static const std::size_t batch = " << b << "UL;"
+ << endl;
+ }
+
// rowvesion
//
bool rv (false);
@@ -43,6 +54,30 @@ namespace relational
os << "static const bool rowversion = " << rv << ";"
<< endl;
}
+
+ virtual void
+ object_public_extra_post (type& c)
+ {
+ bool abst (abstract (c));
+
+ type* poly_root (polymorphic (c));
+ bool poly (poly_root != 0);
+ bool poly_derived (poly && poly_root != &c);
+
+ if (poly_derived || (abst && !poly))
+ return;
+
+ if (semantics::data_member* m = optimistic (c))
+ {
+ sql_type t (parse_sql_type (column_type (*m), *m));
+ if (t.type == sql_type::ROWVERSION)
+ {
+ os << "static version_type" << endl
+ << "version (const id_image_type&);"
+ << endl;
+ }
+ }
+ }
};
entry<class1> class1_entry_;
diff --git a/odb/relational/mssql/source.cxx b/odb/relational/mssql/source.cxx
index 83096ab..ffcfa3c 100644
--- a/odb/relational/mssql/source.cxx
+++ b/odb/relational/mssql/source.cxx
@@ -905,7 +905,7 @@ namespace relational
sql_type t (parse_sql_type (column_type (m), m));
return t.type != sql_type::ROWVERSION
? "1"
- : "sts.update_statement ().version ()";
+ : "version (sts.id_image ())";
}
virtual string
@@ -920,7 +920,7 @@ namespace relational
sql_type::ROWVERSION)
return r;
- // Long data & SQL Server 2005 incompatibility is detected
+ // ROWVERSION & SQL Server 2005 incompatibility is detected
// in persist_statement_extra.
//
r = "OUTPUT INSERTED." +
@@ -933,7 +933,8 @@ namespace relational
struct class_: relational::class_, statement_columns_common
{
- class_ (base const& x): base (x) {}
+ class_ (base const& x):
+ base (x), init_version_value_member_id_image_ ("v", "version_") {}
virtual void
init_image_pre (type& c)
@@ -1036,6 +1037,16 @@ namespace relational
throw operation_failed ();
}
+ // We also cannot support bulk INSERT.
+ //
+ if (c.count ("bulk-persist"))
+ {
+ error (c.location ()) << "in SQL Server 2005 bulk " <<
+ "persist operation cannot be implemented for a " <<
+ "persistent class containing long data" << endl;
+ throw operation_failed ();
+ }
+
r = "; SELECT " + convert_from ("SCOPE_IDENTITY()", *id);
}
@@ -1110,7 +1121,9 @@ namespace relational
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 ()";
+ return t.type != sql_type::ROWVERSION
+ ? "1"
+ : "version (sts.id_image ())";
}
virtual string
@@ -1119,8 +1132,54 @@ namespace relational
sql_type t (parse_sql_type (column_type (m), m));
return t.type != sql_type::ROWVERSION
? "1"
- : "sts.update_statement ().version ()";
+ : "version (sts.id_image ())";
}
+
+ virtual bool
+ optimistic_insert_bind_version (semantics::data_member& m)
+ {
+ sql_type t (parse_sql_type (column_type (m), m));
+ return t.type == sql_type::ROWVERSION;
+ }
+
+ virtual void
+ object_extra (type& c)
+ {
+ bool abst (abstract (c));
+
+ type* poly_root (polymorphic (c));
+ bool poly (poly_root != 0);
+ bool poly_derived (poly && poly_root != &c);
+
+ if (poly_derived || (abst && !poly))
+ return;
+
+ if (semantics::data_member* m = optimistic (c))
+ {
+ sql_type t (parse_sql_type (column_type (*m), *m));
+ if (t.type == sql_type::ROWVERSION)
+ {
+ string const& type (class_fq_name (c));
+ string traits ("access::object_traits_impl< " + type + ", id_" +
+ db.string () + " >");
+
+ os << traits << "::version_type" << endl
+ << traits << "::" << endl
+ << "version (const id_image_type& i)"
+ << "{"
+ << "version_type v;";
+ init_version_value_member_id_image_->traverse (*m);
+ os << "return v;"
+ << "}";
+ }
+ }
+ }
+
+ private:
+ // Go via the dynamic creation to get access to the constructor.
+ //
+ instance<relational::init_value_member>
+ init_version_value_member_id_image_;
};
entry<class_> class_entry_;
}
diff --git a/odb/relational/mysql/context.cxx b/odb/relational/mysql/context.cxx
index 55dd4f7..4ccb206 100644
--- a/odb/relational/mysql/context.cxx
+++ b/odb/relational/mysql/context.cxx
@@ -84,6 +84,7 @@ namespace relational
insert_send_auto_id = true;
delay_freeing_statement_result = false;
need_image_clone = false;
+ generate_bulk = false;
global_index = false;
global_fkey = true;
data_->bind_vector_ = "MYSQL_BIND*";
diff --git a/odb/relational/oracle/context.cxx b/odb/relational/oracle/context.cxx
index 98cda52..be97796 100644
--- a/odb/relational/oracle/context.cxx
+++ b/odb/relational/oracle/context.cxx
@@ -85,6 +85,7 @@ namespace relational
insert_send_auto_id = false;
delay_freeing_statement_result = false;
need_image_clone = true;
+ generate_bulk = true;
global_index = true;
global_fkey = true;
data_->bind_vector_ = "oracle::bind*";
diff --git a/odb/relational/oracle/header.cxx b/odb/relational/oracle/header.cxx
index f87e8de..055ef7b 100644
--- a/odb/relational/oracle/header.cxx
+++ b/odb/relational/oracle/header.cxx
@@ -226,6 +226,36 @@ namespace relational
member_image_type member_image_type_;
};
entry<image_member> image_member_;
+
+ struct class1: relational::class1
+ {
+ class1 (base const& x): base (x) {}
+
+ virtual void
+ object_public_extra_pre (type& c)
+ {
+ bool abst (abstract (c));
+
+ type* poly_root (polymorphic (c));
+ bool poly (poly_root != 0);
+ bool poly_derived (poly && poly_root != &c);
+
+ if (poly_derived || (abst && !poly))
+ return;
+
+ // Bulk operations batch size.
+ //
+ {
+ unsigned long long b (c.count ("bulk")
+ ? c.get<unsigned long long> ("bulk")
+ : 1);
+
+ os << "static const std::size_t batch = " << b << "UL;"
+ << endl;
+ }
+ }
+ };
+ entry<class1> class1_entry_;
}
}
}
diff --git a/odb/relational/pgsql/context.cxx b/odb/relational/pgsql/context.cxx
index d09fa2c..9e91e31 100644
--- a/odb/relational/pgsql/context.cxx
+++ b/odb/relational/pgsql/context.cxx
@@ -84,6 +84,7 @@ namespace relational
insert_send_auto_id = false;
delay_freeing_statement_result = false;
need_image_clone = false;
+ generate_bulk = false;
global_index = true;
global_fkey = false;
data_->bind_vector_ = "pgsql::bind*";
diff --git a/odb/relational/processor.cxx b/odb/relational/processor.cxx
index d32f8e5..b70451a 100644
--- a/odb/relational/processor.cxx
+++ b/odb/relational/processor.cxx
@@ -1024,6 +1024,12 @@ namespace relational
virtual void
traverse_object (type& c)
{
+ // Remove the bulk pragma if this database doesn't support bulk
+ // operations.
+ //
+ if (c.count ("bulk") && !generate_bulk)
+ c.remove ("bulk");
+
// Process indexes. Here we need to do two things: resolve member
// names to member paths and assign names to unnamed indexes. We
// are also going to handle the special container indexes.
diff --git a/odb/relational/source.cxx b/odb/relational/source.cxx
index db288f6..6c8204c 100644
--- a/odb/relational/source.cxx
+++ b/odb/relational/source.cxx
@@ -125,6 +125,7 @@ traverse_object (type& c)
<< db << "::connection&" << (containers || sections ? " c" : "") <<
"," << endl
<< "image_type&" << (sections ? " im" : "") << "," << endl
+ << "id_image_type&" << (sections ? " idim" : "") << "," << endl
<< db << "::binding&" << (containers || sections ? " id" : "") <<
"," << endl
<< db << "::binding&" << (sections ? " idv" : "");
@@ -174,10 +175,27 @@ traverse_object (type& c)
// Functions (abstract and concrete).
//
- // id (image_type)
+ // id(), version()
//
if (!poly_derived && id != 0 && !base_id)
{
+ // id (id_image_type)
+ //
+ if (auto_id)
+ {
+ os << traits << "::id_type" << endl
+ << traits << "::" << endl
+ << "id (const id_image_type& i)"
+ << "{"
+ << db << "::database* db (0);"
+ << "ODB_POTENTIALLY_UNUSED (db);"
+ << endl
+ << "id_type id;";
+ init_id_value_member_id_image_->traverse (*id);
+ os << "return id;"
+ << "}";
+ }
+
// id (image)
//
if (options.generate_query ())
@@ -210,7 +228,7 @@ traverse_object (type& c)
}
}
- // discriminator()
+ // discriminator(image)
//
if (poly && !poly_derived)
{
@@ -1235,6 +1253,29 @@ traverse_object (type& c)
<< "imb.version++;"
<< "}";
+ // Bind id image since that's where the returned id and/or version will
+ // be extracted.
+ //
+ if (!poly_derived)
+ {
+ bool bv (opt != 0 && optimistic_insert_bind_version (*opt));
+ if (bv || auto_id)
+ {
+ os << "{"
+ << "id_image_type& i (sts.id_image ());"
+ << "binding& b (sts.id_image_binding ());"
+ << "if (i.version != sts.id_image_version () || b.version == 0)"
+ << "{"
+ << "bind (b.bind, i);"
+ << "sts.id_image_version (i.version);"
+ << "b.version++;";
+ if (opt != 0)
+ os << "sts.optimistic_id_image_binding ().version++;";
+ os << "}"
+ << "}";
+ }
+ }
+
os << "insert_statement& st (sts.persist_statement ());"
<< "if (!st.execute ())" << endl
<< "throw object_already_persistent ();"
@@ -1248,7 +1289,7 @@ traverse_object (type& c)
os << "// From " << location_string (ma.loc, true) << endl;
if (ma.placeholder ())
- os << ma.translate ("obj", "static_cast< id_type > (st.id ())") << ";"
+ os << ma.translate ("obj", "id (sts.id_image ())") << ";"
<< endl;
else
{
@@ -1265,7 +1306,7 @@ traverse_object (type& c)
if (cast)
os << ")";
- os << " = static_cast< id_type > (st.id ());"
+ os << " = id (sts.id_image ());"
<< endl;
}
}
@@ -1408,6 +1449,173 @@ traverse_object (type& c)
os << "}";
+ // persist() bulk
+ //
+ if (c.count ("bulk-persist"))
+ {
+ os << "void " << traits << "::" << endl
+ << "persist (database& db," << endl
+ << (auto_id ? "" : "const ") << "object_type** objs," << endl
+ << "std::size_t n," << endl
+ << "multiple_exceptions& mex)"
+ << "{"
+ << "ODB_POTENTIALLY_UNUSED (db);"
+ << endl
+ << "using namespace " << db << ";"
+ << endl;
+
+ os << db << "::connection& conn (" << endl
+ << db << "::transaction::current ().connection ());"
+ << "statements_type& sts (" << endl
+ << "conn.statement_cache ().find_object<object_type> ());";
+
+ if (versioned ||
+ uss.count (user_sections::count_new |
+ user_sections::count_all |
+ user_sections::count_versioned_only) != 0)
+ os << "const schema_version_migration& svm (" <<
+ "sts.version_migration (" << schema_name << "));";
+
+ os << endl;
+
+ os << "for (std::size_t i (0); i != n; ++i)"
+ << "{"
+ << "const object_type& obj (*objs[i]);"
+ << "callback (db, obj, callback_event::pre_persist);"
+ //@@ assumption: generate_grow is false
+ //@@ assumption: insert_send_auto_id is false
+ << "init (sts.image (i), obj, statement_insert" <<
+ (versioned ? ", svm" : "") << ");"
+ << "}";
+
+ //@@ assumption: generate_grow is false
+ os << "binding& imb (sts.insert_image_binding ());"
+ << "if (imb.version == 0)"
+ << "{"
+ << "bind (imb.bind, sts.image (), statement_insert" <<
+ (versioned ? ", svm" : "") << ");"
+ << "imb.version++;"
+ << "}";
+
+ // Bind id image since that's where the returned id and/or version will
+ // be extracted.
+ //
+ bool bv (opt != 0 && optimistic_insert_bind_version (*opt));
+ if (bv || auto_id)
+ {
+ os << "binding& idb (sts.id_image_binding ());"
+ //@@ assumption: generate_grow is false
+ << "if (idb.version == 0)"
+ << "{"
+ << "bind (idb.bind, sts.id_image ());"
+ << "idb.version++;";
+ if (opt != 0)
+ os << "sts.optimistic_id_image_binding ().version++;";
+ os << "}";
+ }
+
+ os << "insert_statement& st (sts.persist_statement ());"
+ << "n = st.execute (n, mex);" // Actual number of rows attempted.
+ << endl;
+
+ os << "for (std::size_t i (0); i != n; ++i)"
+ << "{"
+ << "bool r (st.result (i));" // Sets current in mex.
+ << endl
+ << "if (mex[i] != 0)" << endl // Pending exception for this position.
+ << "continue;"
+ << endl
+ << "if (!r)"
+ << "{"
+ << "mex.insert (i, object_already_persistent ());"
+ << "continue;"
+ << "}"
+ << "if (mex.fatal ())" << endl // Don't do any extra work.
+ << "continue;"
+ << endl
+ << (auto_id ? "" : "const ") << "object_type& obj (*objs[i]);"
+ << endl;
+
+ // Extract auto id.
+ //
+ if (auto_id)
+ {
+ member_access& ma (id->get<member_access> ("set"));
+
+ if (!ma.synthesized)
+ os << "// From " << location_string (ma.loc, true) << endl;
+
+ if (ma.placeholder ())
+ os << ma.translate ("obj", "id (sts.id_image (i))") << ";"
+ << endl;
+ 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.direct () && const_type (id->type ()));
+ if (cast)
+ os << "const_cast< id_type& > (" << endl;
+
+ os << ma.translate ("obj");
+
+ if (cast)
+ os << ")";
+
+ os << " = id (sts.id_image (i));"
+ << endl;
+ }
+ }
+
+ // 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;
+
+ data_member& m (*i->member);
+
+ // If the section is soft- added or deleted, check the version.
+ //
+ unsigned long long av (added (m));
+ unsigned long long dv (deleted (m));
+ if (av != 0 || dv != 0)
+ {
+ os << "if (";
+
+ if (av != 0)
+ os << "svm >= schema_version_migration (" << av << "ULL, true)";
+
+ if (av != 0 && dv != 0)
+ os << " &&" << endl;
+
+ if (dv != 0)
+ os << "svm <= schema_version_migration (" << dv << "ULL, true)";
+
+ os << ")" << endl;
+ }
+
+ // Section access is always by reference.
+ //
+ member_access& ma (m.get<member_access> ("get"));
+ if (!ma.synthesized)
+ os << "// From " << location_string (ma.loc, true) << endl;
+
+ os << ma.translate ("obj") << ".reset (true, false);"
+ << endl;
+ }
+
+ os << "callback (db," << endl
+ << (auto_id ? "static_cast<const object_type&> (obj)," : "obj,") << endl
+ << "callback_event::post_persist);"
+ << "}" // for
+ << "}"; // persist ()
+ }
+
// update ()
//
if (id != 0 && (!readonly || poly))
@@ -2071,6 +2279,110 @@ traverse_object (type& c)
os << "}";
}
+ // update () bulk
+ //
+ if (id != 0 && !readonly && c.count ("bulk-update"))
+ {
+ os << "void " << traits << "::" << endl
+ << "update (database& db," << endl
+ << "const object_type** objs," << endl
+ << "std::size_t n," << endl
+ << "multiple_exceptions& mex)"
+ << "{"
+ << "ODB_POTENTIALLY_UNUSED (db);"
+ << endl
+ << "using namespace " << db << ";"
+ << "using " << db << "::update_statement;"
+ << endl
+ << db << "::connection& conn (" << endl
+ << db << "::transaction::current ().connection ());"
+ << "statements_type& sts (" << endl
+ << "conn.statement_cache ().find_object<object_type> ());";
+
+ if (versioned)
+ os << "const schema_version_migration& svm (" <<
+ "sts.version_migration (" << schema_name << "));";
+
+ os << endl
+ << "for (std::size_t i (0); i != n; ++i)"
+ << "{"
+ << "const object_type& obj (*objs[i]);"
+ << "callback (db, obj, callback_event::pre_update);";
+
+ if (!id_ma->synthesized)
+ os << "// From " << location_string (id_ma->loc, true) << endl;
+
+ os << "const id_type& id (" << endl
+ << id_ma->translate ("obj") << ");";
+
+ os << "init (sts.id_image (i), id);"
+ //@@ assumption: generate_grow false
+ << "init (sts.image (i), obj, statement_update);"
+ << "}";
+
+ // Update bindings.
+ //
+ os << "binding& idb (sts.id_image_binding ());"
+ << "binding& imb (sts.update_image_binding ());"
+ << endl;
+
+ //@@ assumption: generate_grow false
+ //
+ os << "bool u (false);" // Avoid incrementing version twice.
+ << "if (imb.version == 0)"
+ << "{"
+ << "bind (imb.bind, sts.image (), statement_update" <<
+ (versioned ? ", svm" : "") << ");"
+ << "imb.version++;"
+ << "u = true;"
+ << "}";
+
+ //@@ assumption: generate_grow false
+ //
+ os << "if (idb.version == 0)"
+ << "{"
+ << "bind (idb.bind, sts.id_image ());"
+ << "idb.version++;";
+ if (opt != 0)
+ os << "sts.optimistic_id_image_binding ().version++;";
+ os << endl
+ << "if (!u)" << endl
+ << "imb.version++;"
+ << "}";
+
+ // We don't need the versioned check here since the statement cannot
+ // be empty; otherwise it would have been an empty object which is
+ // illegal.
+ //
+ os << "update_statement& st (sts.update_statement ());"
+ << "n = st.execute (n, mex);"; // Actual number of rows attempted.
+
+ os << endl
+ << "for (std::size_t i (0); i != n; ++i)"
+ << "{"
+ << "unsigned long long r (st.result (i));" // Also sets current in mex.
+ << endl
+ << "if (mex[i] != 0)" << endl // Pending exception from result().
+ << "continue;"
+ << endl
+ << "if (r != 1)"
+ << "{"
+ << "mex.insert (i," << endl
+ //@@ assumption: result_unknown
+ << "(r == update_statement::result_unknown)," << endl
+ << "object_not_persistent ());"
+ << "continue;"
+ << "}"
+ << "if (mex.fatal ())" << endl // Don't do any extra work.
+ << "continue;"
+ << endl
+ << "const object_type& obj (*objs[i]);"
+ << "callback (db, obj, callback_event::post_update);"
+ << "pointer_cache_traits::update (db, obj);"
+ << "}" // for
+ << "}"; // update()
+ }
+
// erase (id)
//
size_t erase_containers (has_a (c, test_straight_container));
@@ -2204,6 +2516,62 @@ traverse_object (type& c)
os << "}";
}
+ // erase (id) bulk
+ //
+ if (id != 0 && c.count ("bulk-erase"))
+ {
+ os << "std::size_t " << traits << "::" << endl
+ << "erase (database& db," << endl
+ << "const id_type** ids," << endl
+ << "std::size_t n," << endl
+ << "multiple_exceptions& mex)"
+ << "{"
+ << "using namespace " << db << ";"
+ << endl
+ << "ODB_POTENTIALLY_UNUSED (db);"
+ << endl
+ << db << "::connection& conn (" << endl
+ << db << "::transaction::current ().connection ());"
+ << "statements_type& sts (" << endl
+ << "conn.statement_cache ().find_object<object_type> ());"
+ << endl
+ << "for (std::size_t i (0); i != n; ++i)" << endl
+ << "init (sts.id_image (i), *ids[i]);"
+ << endl
+ << "binding& idb (sts.id_image_binding ());"
+ //@@ assumption: generate_grow false
+ << "if (idb.version == 0)"
+ << "{"
+ << "bind (idb.bind, sts.id_image ());"
+ << "idb.version++;"
+ << (opt != 0 ? "sts.optimistic_id_image_binding ().version++;" : "")
+ << "}"
+ << "delete_statement& st (sts.erase_statement ());"
+ << "n = st.execute (n, mex);" // Set to actual number of rows attempted.
+ << endl
+ << "for (std::size_t i (0); i != n; ++i)"
+ << "{"
+ << "unsigned long long r (st.result (i));" // Sets current in mex.
+ << endl
+ << "if (mex[i] != 0)" << endl // Pending exception from result().
+ << "continue;"
+ << endl
+ << "if (r != 1)"
+ << "{"
+ << "mex.insert (i," << endl
+ //@@ assumption: result_unknown
+ << "(r == delete_statement::result_unknown)," << endl
+ << "object_not_persistent ());"
+ << "continue;"
+ << "}"
+ << "if (mex.fatal ())" << endl // Don't do any extra work.
+ << "continue;"
+ << "pointer_cache_traits::erase (db, *ids[i]);"
+ << "}" // for
+ << "return n;"
+ << "}"; // erase()
+ }
+
// erase (object)
//
bool erase_smart_containers (has_a (c, test_smart_container));
@@ -2516,6 +2884,39 @@ traverse_object (type& c)
os << "}";
}
+ // erase (object) bulk
+ //
+ if (id != 0 && c.count ("bulk-erase"))
+ {
+ os << "void " << traits << "::" << endl
+ << "erase (database& db," << endl
+ << "const object_type** objs," << endl
+ << "std::size_t n," << endl
+ << "multiple_exceptions& mex)"
+ << "{"
+ << "id_type a[batch];"
+ << "const id_type* p[batch];"
+ << endl
+ << "for (std::size_t i (0); i != n; ++i)"
+ << "{"
+ << "const object_type& obj (*objs[i]);"
+ << "callback (db, obj, callback_event::pre_erase);"
+ << "a[i] = id (obj);"
+ << "p[i] = a + i;"
+ << "}"
+ << "n = erase (db, p, n, mex);"
+ << endl
+ << "if (mex.fatal ())" << endl // Don't do any extra work.
+ << "return;"
+ << endl
+ << "for (std::size_t i (0); i != n; ++i)"
+ << "{"
+ << "if (mex[i] == 0)" << endl // No pending exception.
+ << "callback (db, *objs[i], callback_event::post_erase);"
+ << "}" // for
+ << "}"; // erase()
+ }
+
// find (id)
//
if (id != 0 && c.default_ctor ())
diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx
index b53b529..3754fcc 100644
--- a/odb/relational/source.hxx
+++ b/odb/relational/source.hxx
@@ -3830,7 +3830,7 @@ namespace relational
os << "," << endl
<< " ";
- os << s.member->name () << " (c, im, id, idv";
+ os << s.member->name () << " (c, im, idim, id, idv";
extra_members ();
os << ")";
}
@@ -5319,6 +5319,7 @@ namespace relational
init_id_image_member_ ("id_", "id"),
init_version_image_member_ ("version_", "(*v)"),
init_id_value_member_ ("id"),
+ init_id_value_member_id_image_ ("id", "id_"),
init_version_value_member_ ("v"),
init_named_version_value_member_ ("v", "version_"),
init_discriminator_value_member_ ("d", "", false),
@@ -5344,6 +5345,7 @@ namespace relational
init_id_image_member_ ("id_", "id"),
init_version_image_member_ ("version_", "(*v)"),
init_id_value_member_ ("id"),
+ init_id_value_member_id_image_ ("id", "id_"),
init_version_value_member_ ("v"),
init_named_version_value_member_ ("v", "version_"),
init_discriminator_value_member_ ("d", "", false),
@@ -5502,6 +5504,12 @@ namespace relational
return "1";
}
+ virtual bool
+ optimistic_insert_bind_version (semantics::data_member&)
+ {
+ return false;
+ }
+
virtual void
traverse_object (type& c);
@@ -5752,6 +5760,7 @@ namespace relational
traversal::names init_value_member_names_;
instance<init_value_member> init_id_value_member_;
+ instance<init_value_member> init_id_value_member_id_image_;
instance<init_value_member> init_version_value_member_;
instance<init_value_member> init_named_version_value_member_;
instance<init_value_member> init_discriminator_value_member_;
diff --git a/odb/relational/sqlite/context.cxx b/odb/relational/sqlite/context.cxx
index f347d30..76c2b02 100644
--- a/odb/relational/sqlite/context.cxx
+++ b/odb/relational/sqlite/context.cxx
@@ -86,6 +86,7 @@ namespace relational
insert_send_auto_id = true;
delay_freeing_statement_result = false;
need_image_clone = false;
+ generate_bulk = false;
global_index = true;
global_fkey = false;
data_->bind_vector_ = "sqlite::bind*";
diff --git a/odb/relational/validator.cxx b/odb/relational/validator.cxx
index 3c8c327..6c3a6fd 100644
--- a/odb/relational/validator.cxx
+++ b/odb/relational/validator.cxx
@@ -387,6 +387,66 @@ namespace relational
names (c, data_member_names_);
+ // Validate bulk operation support.
+ //
+ for (bool i (true); i && c.count ("bulk"); i = false)
+ {
+ location_t l (c.get<location_t> ("bulk-location"));
+
+ if (polymorphic (c))
+ {
+ error (l) << "bulk operations on polymorphic objects are "
+ "not supported" << endl;
+ valid_ = false;
+ break;
+ }
+
+ if (has_a (c, test_straight_container))
+ {
+ error (l) << "bulk operations on objects with containers are "
+ "not supported" << endl;
+ valid_ = false;
+ break;
+ }
+
+ if (optimistic (c))
+ {
+ error (l) << "bulk operations on optimistic objects are not "
+ "supported" << endl;
+ valid_ = false;
+ break;
+ }
+
+ bool update (true);
+
+ // If we have a change-updated section, then we cannot generate
+ // the bulk update operation.
+ //
+ user_sections& uss (c.get<user_sections> ("user-sections"));
+
+ for (user_sections::iterator i (uss.begin ());
+ update && i != uss.end ();
+ ++i)
+ {
+ const user_section& s (*i);
+
+ // Skip special sections.
+ //
+ if (s.special != user_section::special_ordinary)
+ continue;
+
+ // Always-updated section still needs a separate statement
+ // (since it may not be loaded).
+ //
+ if (!s.update_empty () && s.update != user_section::update_manual)
+ update = false;
+ }
+
+ c.set ("bulk-persist", true);
+ if (update) c.set ("bulk-update", true);
+ c.set ("bulk-erase", true);
+ }
+
// Validate indexes.
//
{