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:45:34 +0200
commitb39e670403bfdff6870d9b9c7b075230e84c27cf (patch)
treedd7a8ee8cd96b0543a629e5db73680050a317e1c
parent40052e8f41978087836d04a37d1dd86552e16b7b (diff)
Implement bulk database operation support for Oracle and SQL Server
-rw-r--r--odb/oracle/binding.hxx22
-rw-r--r--odb/oracle/container-statements.hxx2
-rw-r--r--odb/oracle/database.hxx31
-rw-r--r--odb/oracle/database.ixx35
-rw-r--r--odb/oracle/error.cxx21
-rw-r--r--odb/oracle/error.hxx12
-rw-r--r--odb/oracle/exceptions.cxx24
-rw-r--r--odb/oracle/exceptions.hxx12
-rw-r--r--odb/oracle/no-id-object-statements.hxx9
-rw-r--r--odb/oracle/no-id-object-statements.txx10
-rw-r--r--odb/oracle/oracle-types.hxx3
-rw-r--r--odb/oracle/polymorphic-object-statements.hxx8
-rw-r--r--odb/oracle/section-statements.hxx3
-rw-r--r--odb/oracle/section-statements.txx2
-rw-r--r--odb/oracle/simple-object-statements.hxx69
-rw-r--r--odb/oracle/simple-object-statements.txx25
-rw-r--r--odb/oracle/statement.cxx976
-rw-r--r--odb/oracle/statement.hxx238
-rw-r--r--odb/oracle/statement.ixx100
19 files changed, 1179 insertions, 423 deletions
diff --git a/odb/oracle/binding.hxx b/odb/oracle/binding.hxx
index 94e7128..e2d3fa5 100644
--- a/odb/oracle/binding.hxx
+++ b/odb/oracle/binding.hxx
@@ -11,6 +11,7 @@
#include <odb/oracle/version.hxx>
#include <odb/oracle/oracle-types.hxx>
+#include <odb/oracle/auto-handle.hxx>
#include <odb/oracle/details/export.hxx>
@@ -24,10 +25,23 @@ namespace odb
typedef oracle::bind bind_type;
typedef oracle::change_callback change_callback_type;
- binding (): bind (0), count (0), version (0), change_callback (0) {}
+ binding ()
+ : bind (0), count (0), version (0),
+ batch (0), skip (0), status (0),
+ change_callback (0) {}
binding (bind_type* b, std::size_t n)
- : bind (b), count (n), version (0), change_callback (0)
+ : bind (b), count (n), version (0),
+ batch (1), skip (0), status (0),
+ change_callback (0)
+ {
+ }
+
+ binding (bind_type* b, std::size_t n,
+ std::size_t bt, std::size_t s, sb4* st)
+ : bind (b), count (n), version (0),
+ batch (bt), skip (s), status (st),
+ change_callback (0)
{
}
@@ -35,6 +49,10 @@ namespace odb
std::size_t count;
std::size_t version;
+ std::size_t batch;
+ std::size_t skip;
+ sb4* status; // Batch status array.
+
change_callback_type* change_callback;
private:
diff --git a/odb/oracle/container-statements.hxx b/odb/oracle/container-statements.hxx
index ef1733d..a0a1742 100644
--- a/odb/oracle/container-statements.hxx
+++ b/odb/oracle/container-statements.hxx
@@ -122,7 +122,7 @@ namespace odb
insert_text_,
versioned_, // Process if versioned.
insert_image_binding_,
- false));
+ 0));
return *insert_;
}
diff --git a/odb/oracle/database.hxx b/odb/oracle/database.hxx
index d40611f..cea2bf6 100644
--- a/odb/oracle/database.hxx
+++ b/odb/oracle/database.hxx
@@ -95,6 +95,10 @@ namespace odb
template <typename T>
typename object_traits<T>::id_type
+ persist (const T& object);
+
+ template <typename T>
+ typename object_traits<T>::id_type
persist (T* obj_ptr);
template <typename T, template <typename> class P>
@@ -117,6 +121,13 @@ namespace odb
typename object_traits<T>::id_type
persist (const typename object_traits<T>::pointer_type& obj_ptr);
+ // Bulk persist. Can be a range of references or pointers (including
+ // smart pointers) to objects.
+ //
+ template <typename I>
+ void
+ persist (I begin, I end, bool continue_failed = true);
+
// Load an object. Throw object_not_persistent if not found.
//
template <typename T>
@@ -203,6 +214,13 @@ namespace odb
void
update (const typename object_traits<T>::pointer_type& obj_ptr);
+ // Bulk update. Can be a range of references or pointers (including
+ // smart pointers) to objects.
+ //
+ template <typename I>
+ void
+ update (I begin, I end, bool continue_failed = true);
+
// Update a section of an object. Throws the section_not_loaded
// exception if the section is not loaded. Note also that this
// function does not clear the changed flag if it is set.
@@ -246,6 +264,19 @@ namespace odb
void
erase (const typename object_traits<T>::pointer_type& obj_ptr);
+ // Bulk erase.
+ //
+ template <typename T, typename I>
+ void
+ erase (I id_begin, I id_end, bool continue_failed = true);
+
+ // Can be a range of references or pointers (including smart pointers)
+ // to objects.
+ //
+ template <typename I>
+ void
+ erase (I obj_begin, I obj_end, bool continue_failed = true);
+
// Erase multiple objects matching a query predicate.
//
template <typename T>
diff --git a/odb/oracle/database.ixx b/odb/oracle/database.ixx
index d3b56ae..e6a698e 100644
--- a/odb/oracle/database.ixx
+++ b/odb/oracle/database.ixx
@@ -27,6 +27,13 @@ namespace odb
template <typename T>
inline typename object_traits<T>::id_type database::
+ persist (const T& obj)
+ {
+ return persist_<const T, id_oracle> (obj);
+ }
+
+ template <typename T>
+ inline typename object_traits<T>::id_type database::
persist (T* p)
{
typedef typename object_traits<T>::pointer_type object_pointer;
@@ -93,6 +100,13 @@ namespace odb
return persist_<T, id_oracle> (pobj);
}
+ template <typename I>
+ inline void database::
+ persist (I b, I e, bool cont)
+ {
+ persist_<I, id_oracle> (b, e, cont);
+ }
+
template <typename T>
inline typename object_traits<T>::pointer_type database::
load (const typename object_traits<T>::id_type& id)
@@ -254,6 +268,13 @@ namespace odb
update_<T, id_oracle> (pobj);
}
+ template <typename I>
+ inline void database::
+ update (I b, I e, bool cont)
+ {
+ update_<I, id_oracle> (b, e, cont);
+ }
+
template <typename T>
inline void database::
update (const T& obj, const section& s)
@@ -343,6 +364,20 @@ namespace odb
erase_<T, id_oracle> (pobj);
}
+ template <typename T, typename I>
+ inline void database::
+ erase (I idb, I ide, bool cont)
+ {
+ erase_id_<I, T, id_oracle> (idb, ide, cont);
+ }
+
+ template <typename I>
+ inline void database::
+ erase (I ob, I oe, bool cont)
+ {
+ erase_object_<I, id_oracle> (ob, oe, cont);
+ }
+
template <typename T>
inline unsigned long long database::
erase_query ()
diff --git a/odb/oracle/error.cxx b/odb/oracle/error.cxx
index afab2c7..68bdc44 100644
--- a/odb/oracle/error.cxx
+++ b/odb/oracle/error.cxx
@@ -19,7 +19,8 @@ namespace odb
namespace oracle
{
static void
- translate_error (void* h, ub4 htype, sword r, connection* conn)
+ translate_error (void* h, ub4 htype, sword r, connection* conn,
+ size_t pos, multiple_exceptions* mex)
{
assert (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO);
@@ -186,25 +187,33 @@ namespace odb
dbe.append (e, b);
}
- throw dbe;
+ if (mex == 0)
+ throw dbe;
+ else
+ // It could be that some of these errors are fatal. I guess we
+ // will just have to learn from experience which ones are. The
+ // client code can always treat specific error codes as fatal.
+ //
+ mex->insert (pos, dbe);
}
void
- translate_error (OCIError* h, sword r)
+ translate_error (OCIError* h, sword r, connection* c,
+ size_t pos, multiple_exceptions* mex)
{
- translate_error (h, OCI_HTYPE_ERROR, r, 0);
+ translate_error (h, OCI_HTYPE_ERROR, r, c, pos, mex);
}
void
translate_error (connection& c, sword r)
{
- translate_error (c.error_handle (), OCI_HTYPE_ERROR, r, &c);
+ translate_error (c.error_handle (), OCI_HTYPE_ERROR, r, &c, 0, 0);
}
void
translate_error (OCIEnv* h)
{
- translate_error (h, OCI_HTYPE_ENV, OCI_ERROR, 0);
+ translate_error (h, OCI_HTYPE_ENV, OCI_ERROR, 0, 0, 0);
}
}
}
diff --git a/odb/oracle/error.hxx b/odb/oracle/error.hxx
index fdb0537..accfc3f 100644
--- a/odb/oracle/error.hxx
+++ b/odb/oracle/error.hxx
@@ -7,20 +7,24 @@
#include <odb/pre.hxx>
+#include <cstddef> // std::size_t
+
#include <odb/oracle/oracle-fwd.hxx>
-#include <odb/oracle/forward.hxx>
+#include <odb/oracle/forward.hxx> // connection, multiple_exceptions
#include <odb/oracle/version.hxx>
+
#include <odb/oracle/details/export.hxx>
namespace odb
{
namespace oracle
{
- // Translate OCI error given an error handle and throw an appropriate
- // exception.
+ // Translate OCI error given an error handle and throw (or return,
+ // in case multiple_exceptions is not NULL) an appropriate exception.
//
LIBODB_ORACLE_EXPORT void
- translate_error (OCIError*, sword result);
+ translate_error (OCIError*, sword result, connection* = 0,
+ std::size_t pos = 0, multiple_exceptions* = 0);
LIBODB_ORACLE_EXPORT void
translate_error (connection&, sword result);
diff --git a/odb/oracle/exceptions.cxx b/odb/oracle/exceptions.cxx
index b6375e7..4676843 100644
--- a/odb/oracle/exceptions.cxx
+++ b/odb/oracle/exceptions.cxx
@@ -57,6 +57,12 @@ namespace odb
return what_.c_str ();
}
+ database_exception* database_exception::
+ clone () const
+ {
+ return new database_exception (*this);
+ }
+
//
// lob_comparison
//
@@ -67,6 +73,12 @@ namespace odb
return "comparison of LOB values in queries not supported";
}
+ lob_comparison* lob_comparison::
+ clone () const
+ {
+ return new lob_comparison (*this);
+ }
+
//
// cli_exception
//
@@ -88,6 +100,12 @@ namespace odb
return what_.c_str ();
}
+ cli_exception* cli_exception::
+ clone () const
+ {
+ return new cli_exception (*this);
+ }
+
//
// invalid_oci_handle
//
@@ -97,5 +115,11 @@ namespace odb
{
return "invalid oci handle passed or unable to allocate handle";
}
+
+ invalid_oci_handle* invalid_oci_handle::
+ clone () const
+ {
+ return new invalid_oci_handle (*this);
+ }
}
}
diff --git a/odb/oracle/exceptions.hxx b/odb/oracle/exceptions.hxx
index d8ca14a..87327b9 100644
--- a/odb/oracle/exceptions.hxx
+++ b/odb/oracle/exceptions.hxx
@@ -76,6 +76,9 @@ namespace odb
virtual const char*
what () const throw ();
+ virtual database_exception*
+ clone () const;
+
void
append (sb4 error, const std::string& message);
@@ -88,6 +91,9 @@ namespace odb
{
virtual const char*
what () const throw ();
+
+ virtual lob_comparison*
+ clone () const;
};
struct LIBODB_ORACLE_EXPORT cli_exception: odb::exception
@@ -98,6 +104,9 @@ namespace odb
virtual const char*
what () const throw ();
+ virtual cli_exception*
+ clone () const;
+
private:
std::string what_;
};
@@ -106,6 +115,9 @@ namespace odb
{
virtual const char*
what () const throw ();
+
+ virtual invalid_oci_handle*
+ clone () const;
};
namespace core
diff --git a/odb/oracle/no-id-object-statements.hxx b/odb/oracle/no-id-object-statements.hxx
index 0be8dac..70f76e4 100644
--- a/odb/oracle/no-id-object-statements.hxx
+++ b/odb/oracle/no-id-object-statements.hxx
@@ -49,9 +49,9 @@ namespace odb
// Object image.
//
image_type&
- image ()
+ image (std::size_t i = 0)
{
- return image_;
+ return image_[i];
}
// Insert binding.
@@ -88,7 +88,7 @@ namespace odb
object_traits::persist_statement,
object_traits::versioned, // Process if versioned.
insert_image_binding_,
- false));
+ 0));
return *persist_;
}
@@ -108,7 +108,8 @@ namespace odb
no_id_object_statements& operator= (const no_id_object_statements&);
private:
- image_type image_;
+ image_type image_[object_traits::batch];
+ sb4 status_[object_traits::batch];
// Select binding.
//
diff --git a/odb/oracle/no-id-object-statements.txx b/odb/oracle/no-id-object-statements.txx
index 31ed931..31f3891 100644
--- a/odb/oracle/no-id-object-statements.txx
+++ b/odb/oracle/no-id-object-statements.txx
@@ -19,13 +19,17 @@ namespace odb
no_id_object_statements (connection_type& conn)
: statements_base (conn),
select_image_binding_ (select_image_bind_, select_column_count),
- insert_image_binding_ (insert_image_bind_, insert_column_count)
+ insert_image_binding_ (insert_image_bind_,
+ insert_column_count,
+ object_traits::batch,
+ sizeof (image_type),
+ status_)
{
- image_.version = 0;
+ image_[0].version = 0;
select_image_version_ = 0;
insert_image_version_ = 0;
- select_image_binding_.change_callback = image_.change_callback ();
+ select_image_binding_.change_callback = image_[0].change_callback ();
std::memset (insert_image_bind_, 0, sizeof (insert_image_bind_));
std::memset (select_image_bind_, 0, sizeof (select_image_bind_));
diff --git a/odb/oracle/oracle-types.hxx b/odb/oracle/oracle-types.hxx
index ec53425..ffc974a 100644
--- a/odb/oracle/oracle-types.hxx
+++ b/odb/oracle/oracle-types.hxx
@@ -115,7 +115,8 @@ namespace odb
// bindings, this is interpreted as the OCIDefine
// handle associated with the LOB result parameter.
ub4 capacity; // The maximum number of bytes that can be stored in
- // the buffer.
+ // the buffer. For LOBs, it used to store array skip
+ // size.
sb2* indicator; // Pointer to an OCI indicator variable.
lob_callback* callback;
diff --git a/odb/oracle/polymorphic-object-statements.hxx b/odb/oracle/polymorphic-object-statements.hxx
index 81ea3c3..58ecdaa 100644
--- a/odb/oracle/polymorphic-object-statements.hxx
+++ b/odb/oracle/polymorphic-object-statements.hxx
@@ -306,7 +306,7 @@ namespace odb
object_traits::persist_statement,
object_traits::versioned, // Process if versioned.
insert_image_binding_,
- false));
+ 0));
return *persist_;
}
@@ -366,6 +366,7 @@ namespace odb
return extra_statement_cache_.get (
conn_,
image_,
+ id_image (),
id_image_binding (),
&id_image_binding ()); // Note, not id+version.
}
@@ -404,8 +405,9 @@ namespace odb
root_statements_type& root_statements_;
base_statements_type& base_statements_;
- extra_statement_cache_ptr<extra_statement_cache_type, image_type>
- extra_statement_cache_;
+ extra_statement_cache_ptr<extra_statement_cache_type,
+ image_type,
+ id_image_type> extra_statement_cache_;
image_type image_;
diff --git a/odb/oracle/section-statements.hxx b/odb/oracle/section-statements.hxx
index 3d32b65..eb61cc1 100644
--- a/odb/oracle/section-statements.hxx
+++ b/odb/oracle/section-statements.hxx
@@ -36,6 +36,7 @@ namespace odb
typedef ST traits;
typedef typename traits::image_type image_type;
+ typedef typename traits::id_image_type id_image_type;
typedef oracle::select_statement select_statement_type;
typedef oracle::update_statement update_statement_type;
@@ -43,7 +44,7 @@ namespace odb
typedef oracle::connection connection_type;
section_statements (connection_type&,
- image_type&,
+ image_type&, id_image_type&,
binding& id, binding& idv);
connection_type&
diff --git a/odb/oracle/section-statements.txx b/odb/oracle/section-statements.txx
index 391550f..583b706 100644
--- a/odb/oracle/section-statements.txx
+++ b/odb/oracle/section-statements.txx
@@ -11,7 +11,7 @@ namespace odb
template <typename T, typename ST>
section_statements<T, ST>::
section_statements (connection_type& conn,
- image_type& im,
+ image_type& im, id_image_type&,
binding& id, binding& idv)
: conn_ (conn),
svm_ (0),
diff --git a/odb/oracle/simple-object-statements.hxx b/odb/oracle/simple-object-statements.hxx
index ff76e91..0ee263a 100644
--- a/odb/oracle/simple-object-statements.hxx
+++ b/odb/oracle/simple-object-statements.hxx
@@ -39,49 +39,56 @@ namespace odb
// deleter function which will be initialized during allocation
// (at that point we know that the cache class is defined).
//
- template <typename T, typename I>
+ template <typename T, typename I, typename ID>
struct extra_statement_cache_ptr
{
typedef I image_type;
+ typedef ID id_image_type;
typedef oracle::connection connection_type;
extra_statement_cache_ptr (): p_ (0) {}
~extra_statement_cache_ptr ()
{
if (p_ != 0)
- (this->*deleter_) (0, 0, 0, 0);
+ (this->*deleter_) (0, 0, 0, 0, 0);
}
T&
- get (connection_type& c, image_type& im, binding& id, binding* idv)
+ get (connection_type& c,
+ image_type& im, id_image_type& idim,
+ binding& id, binding* idv)
{
if (p_ == 0)
- allocate (&c, &im, &id, (idv != 0 ? idv : &id));
+ allocate (&c, &im, &idim, &id, (idv != 0 ? idv : &id));
return *p_;
}
private:
void
- allocate (connection_type*, image_type*, binding*, binding*);
+ allocate (connection_type*,
+ image_type*, id_image_type*,
+ binding*, binding*);
private:
T* p_;
void (extra_statement_cache_ptr::*deleter_) (
- connection_type*, image_type*, binding*, binding*);
+ connection_type*, image_type*, id_image_type*, binding*, binding*);
};
- template <typename T, typename I>
- void extra_statement_cache_ptr<T, I>::
- allocate (connection_type* c, image_type* im, binding* id, binding* idv)
+ template <typename T, typename I, typename ID>
+ void extra_statement_cache_ptr<T, I, ID>::
+ allocate (connection_type* c,
+ image_type* im, id_image_type* idim,
+ binding* id, binding* idv)
{
// To reduce object code size, this function acts as both allocator
// and deleter.
//
if (p_ == 0)
{
- p_ = new T (*c, *im, *id, *idv);
- deleter_ = &extra_statement_cache_ptr<T, I>::allocate;
+ p_ = new T (*c, *im, *idim, *id, *idv);
+ deleter_ = &extra_statement_cache_ptr<T, I, ID>::allocate;
}
else
delete p_;
@@ -276,10 +283,7 @@ namespace odb
// Object image.
//
image_type&
- image ()
- {
- return image_;
- }
+ image (std::size_t i = 0) {return images_[i].obj;}
// Insert binding.
//
@@ -323,7 +327,7 @@ namespace odb
// Object id image and binding.
//
id_image_type&
- id_image () {return id_image_;}
+ id_image (std::size_t i = 0) {return images_[i].id;}
std::size_t
id_image_version () const {return id_image_version_;}
@@ -353,7 +357,7 @@ namespace odb
object_traits::persist_statement,
object_traits::versioned, // Process if versioned.
insert_image_binding_,
- object_traits::auto_id));
+ object_traits::auto_id ? &id_image_binding_ : 0));
return *persist_;
}
@@ -383,7 +387,8 @@ namespace odb
new (details::shared) update_statement_type (
conn_,
object_traits::update_statement,
- object_traits::versioned, // Process if versioned.
+ true, // Unique (0 or 1).
+ object_traits::versioned, // Process if versioned.
update_image_binding_));
return *update_;
@@ -397,6 +402,7 @@ namespace odb
new (details::shared) delete_statement_type (
conn_,
object_traits::erase_statement,
+ true, // Unique (0 or 1 affected rows).
id_image_binding_));
return *erase_;
@@ -423,7 +429,9 @@ namespace odb
extra_statement_cache ()
{
return extra_statement_cache_.get (
- conn_, image_, id_image_binding_, od_.id_image_binding ());
+ conn_,
+ images_[0].obj, images_[0].id,
+ id_image_binding_, od_.id_image_binding ());
}
public:
@@ -470,10 +478,22 @@ namespace odb
template <typename T1>
friend class polymorphic_derived_object_statements;
- extra_statement_cache_ptr<extra_statement_cache_type, image_type>
- extra_statement_cache_;
+ extra_statement_cache_ptr<extra_statement_cache_type,
+ image_type,
+ id_image_type> extra_statement_cache_;
+
+ // The UPDATE statement uses both the object and id image. Keep
+ // them next to each other so that the same skip distance can
+ // be used in batch binding.
+ //
+ struct images
+ {
+ image_type obj;
+ id_image_type id;
+ };
- image_type image_;
+ images images_[object_traits::batch];
+ sb4 status_[object_traits::batch];
// Select binding.
//
@@ -502,10 +522,9 @@ namespace odb
bind update_image_bind_[update_column_count + id_column_count +
managed_optimistic_column_count];
- // Id image binding (only used as a parameter). Uses the suffix in
- // the update bind.
+ // Id image binding (only used as a parameter or in RETURNING for
+ // auto ids). Uses the suffix in the update bind.
//
- id_image_type id_image_;
std::size_t id_image_version_;
binding id_image_binding_;
diff --git a/odb/oracle/simple-object-statements.txx b/odb/oracle/simple-object-statements.txx
index 61bbc5f..0e42d31 100644
--- a/odb/oracle/simple-object-statements.txx
+++ b/odb/oracle/simple-object-statements.txx
@@ -43,24 +43,35 @@ namespace odb
object_statements (connection_type& conn)
: object_statements_base (conn),
select_image_binding_ (select_image_bind_, select_column_count),
- insert_image_binding_ (insert_image_bind_, insert_column_count),
+ insert_image_binding_ (insert_image_bind_,
+ insert_column_count,
+ object_traits::batch,
+ sizeof (images),
+ status_),
update_image_binding_ (update_image_bind_,
update_column_count + id_column_count +
- managed_optimistic_column_count),
+ managed_optimistic_column_count,
+ object_traits::batch,
+ sizeof (images),
+ status_),
id_image_binding_ (update_image_bind_ + update_column_count,
- id_column_count),
+ id_column_count,
+ object_traits::batch,
+ sizeof (images),
+ status_),
od_ (update_image_bind_ + update_column_count)
{
- image_.version = 0;
+ images_[0].obj.version = 0; // @@ TODO [0]
+ images_[0].id.version = 0; // @@ TODO
+
select_image_version_ = 0;
insert_image_version_ = 0;
update_image_version_ = 0;
update_id_image_version_ = 0;
-
- id_image_.version = 0;
id_image_version_ = 0;
- select_image_binding_.change_callback = image_.change_callback ();
+ select_image_binding_.change_callback =
+ images_[0].obj.change_callback ();
std::memset (insert_image_bind_, 0, sizeof (insert_image_bind_));
std::memset (update_image_bind_, 0, sizeof (update_image_bind_));
diff --git a/odb/oracle/statement.cxx b/odb/oracle/statement.cxx
index 4032138..70577fa 100644
--- a/odb/oracle/statement.cxx
+++ b/odb/oracle/statement.cxx
@@ -4,7 +4,7 @@
#include <oci.h>
-#include <cstring> // std::strlen
+#include <cstring> // std::strlen, std::memset
#include <cassert>
#include <odb/tracer.hxx>
@@ -70,10 +70,18 @@ namespace odb
SQLT_CLOB // bind::nclob
};
+ template <typename T>
+ inline T*
+ offset (T* base, size_t count, size_t size)
+ {
+ return reinterpret_cast<T*> (
+ reinterpret_cast<char*> (base) + count * size);
+ }
+
extern "C" sb4
odb_oracle_param_callback_proxy (void* context,
OCIBind*,
- ub4, // iteration
+ ub4 it, // iteration
ub4, // index
void** buffer,
ub4* size,
@@ -81,15 +89,23 @@ namespace odb
void** indicator)
{
bind& b (*static_cast<bind*> (context));
- lob* l (static_cast<lob*> (b.buffer));
+
+ // Offset the data based on the current iteration and skip size (stored
+ // in capacity).
+ //
+ sb2* ind (offset (b.indicator, it, b.capacity));
// Only call the callback if the parameter is not NULL.
//
- if (*b.indicator != -1)
+ if (*ind != -1)
{
+ lob* l (static_cast<lob*> (offset (b.buffer, it, b.capacity)));
+ lob_callback* cb (
+ static_cast<lob_callback*> (offset (b.callback, it, b.capacity)));
+
chunk_position pos;
- if (!(*b.callback->callback.param) (
- b.callback->context.param,
+ if (!(*cb->callback.param) (
+ cb->context.param,
&l->position,
const_cast<const void**> (buffer),
size,
@@ -125,7 +141,7 @@ namespace odb
else
*piece = OCI_ONE_PIECE;
- *indicator = b.indicator;
+ *indicator = ind;
return OCI_CONTINUE;
}
@@ -321,7 +337,7 @@ namespace odb
}
ub4 statement::
- bind_param (bind* b, size_t n)
+ bind_param (bind* b, size_t n, size_t batch, size_t skip)
{
// Figure out how many unbind elements we will need and allocate them.
//
@@ -361,6 +377,11 @@ namespace odb
}
}
+ // Unbind is only used in queries which means there should be no
+ // batches.
+ //
+ assert (un == 0 || batch == 1);
+
if (un != 0)
udata_ = new unbind[un];
}
@@ -387,186 +408,210 @@ namespace odb
{
case bind::timestamp:
{
- datetime* dt (static_cast<datetime*> (b->buffer));
-
- if (dt->descriptor == 0)
+ for (size_t i (0); i != batch; ++i)
{
- void* d (0);
- r = OCIDescriptorAlloc (env,
- &d,
- OCI_DTYPE_TIMESTAMP,
- 0,
- 0);
+ datetime* dt (
+ static_cast<datetime*> (offset (b->buffer, i, skip)));
- if (r != OCI_SUCCESS)
- throw invalid_oci_handle ();
-
- if (dt->flags & descriptor_cache)
- {
- dt->descriptor = static_cast<OCIDateTime*> (d);
- dt->environment = env;
- dt->error = err;
- }
+ void* pd (0); // Pointer to descriptor.
- // If the datetime instance is not responsible for the
- // descriptor, then we have to arrange to have it freed
- // using the unbind machinery.
- //
- if ((dt->flags & descriptor_free) == 0)
+ if (dt->descriptor == 0)
{
- unbind& u (udata_[usize_++]);
-
- u.type = bind::timestamp;
- u.bind = (dt->flags & descriptor_cache) ? b : 0;
- u.value = d;
- value = &u.value;
+ void* d (0);
+ r = OCIDescriptorAlloc (env,
+ &d,
+ OCI_DTYPE_TIMESTAMP,
+ 0,
+ 0);
+
+ if (r != OCI_SUCCESS)
+ throw invalid_oci_handle ();
+
+ if (dt->flags & descriptor_cache)
+ {
+ dt->descriptor = static_cast<OCIDateTime*> (d);
+ dt->environment = env;
+ dt->error = err;
+ }
+
+ // If the datetime instance is not responsible for the
+ // descriptor, then we have to arrange to have it freed
+ // using the unbind machinery.
+ //
+ if ((dt->flags & descriptor_free) == 0)
+ {
+ unbind& u (udata_[usize_++]);
+
+ u.type = bind::timestamp;
+ u.bind = (dt->flags & descriptor_cache) ? b : 0;
+ u.value = d;
+ pd = &u.value;
+ }
+ else
+ pd = &dt->descriptor;
+
+ // Initialize the descriptor from the cached data.
+ //
+ if (b->indicator == 0 || *b->indicator != -1)
+ r = OCIDateTimeConstruct (env,
+ err,
+ static_cast<OCIDateTime*> (d),
+ dt->year_,
+ dt->month_,
+ dt->day_,
+ dt->hour_,
+ dt->minute_,
+ dt->second_,
+ dt->nanosecond_,
+ 0,
+ 0);
+
+ if (r != OCI_SUCCESS)
+ translate_error (err, r);
}
else
- value = &dt->descriptor;
-
- // Initialize the descriptor from the cached data.
- //
- if (b->indicator == 0 || *b->indicator != -1)
- r = OCIDateTimeConstruct (env,
- err,
- static_cast<OCIDateTime*> (d),
- dt->year_,
- dt->month_,
- dt->day_,
- dt->hour_,
- dt->minute_,
- dt->second_,
- dt->nanosecond_,
- 0,
- 0);
+ pd = &dt->descriptor;
- if (r != OCI_SUCCESS)
- translate_error (err, r);
+ if (i == 0)
+ value = pd;
}
- else
- value = &dt->descriptor;
capacity = static_cast<sb4> (sizeof (OCIDateTime*));
-
break;
}
case bind::interval_ym:
{
- interval_ym* iym (static_cast<interval_ym*> (b->buffer));
-
- if (iym->descriptor == 0)
+ for (size_t i (0); i != batch; ++i)
{
- void* d (0);
- r = OCIDescriptorAlloc (env,
- &d,
- OCI_DTYPE_INTERVAL_YM,
- 0,
- 0);
+ interval_ym* iym (
+ static_cast<interval_ym*> (offset (b->buffer, i, skip)));
- if (r != OCI_SUCCESS)
- throw invalid_oci_handle ();
+ void* pd (0); // Pointer to descriptor.
- if (iym->flags & descriptor_cache)
+ if (iym->descriptor == 0)
{
- iym->descriptor = static_cast<OCIInterval*> (d);
- iym->environment = env;
- iym->error = err;
- }
-
- // If the interval_ym instance is not responsible for the
- // descriptor, then we have to arrange to have it freed
- // using the unbind machinery.
- //
- if ((iym->flags & descriptor_free) == 0)
- {
- unbind& u (udata_[usize_++]);
-
- u.type = bind::interval_ym;
- u.bind = (iym->flags & descriptor_cache) ? b : 0;
- u.value = d;
- value = &u.value;
+ void* d (0);
+ r = OCIDescriptorAlloc (env,
+ &d,
+ OCI_DTYPE_INTERVAL_YM,
+ 0,
+ 0);
+
+ if (r != OCI_SUCCESS)
+ throw invalid_oci_handle ();
+
+ if (iym->flags & descriptor_cache)
+ {
+ iym->descriptor = static_cast<OCIInterval*> (d);
+ iym->environment = env;
+ iym->error = err;
+ }
+
+ // If the interval_ym instance is not responsible for the
+ // descriptor, then we have to arrange to have it freed
+ // using the unbind machinery.
+ //
+ if ((iym->flags & descriptor_free) == 0)
+ {
+ unbind& u (udata_[usize_++]);
+
+ u.type = bind::interval_ym;
+ u.bind = (iym->flags & descriptor_cache) ? b : 0;
+ u.value = d;
+ pd = &u.value;
+ }
+ else
+ pd = &iym->descriptor;
+
+ // Initialize the descriptor from the cached data.
+ //
+ if (b->indicator == 0 || *b->indicator != -1)
+ r = OCIIntervalSetYearMonth (env,
+ err,
+ iym->year_,
+ iym->month_,
+ static_cast<OCIInterval*> (d));
+
+ if (r != OCI_SUCCESS)
+ translate_error (err, r);
}
else
- value = &iym->descriptor;
-
- // Initialize the descriptor from the cached data.
- //
- if (b->indicator == 0 || *b->indicator != -1)
- r = OCIIntervalSetYearMonth (env,
- err,
- iym->year_,
- iym->month_,
- static_cast<OCIInterval*> (d));
+ pd = &iym->descriptor;
- if (r != OCI_SUCCESS)
- translate_error (err, r);
+ if (i == 0)
+ value = pd;
}
- else
- value = &iym->descriptor;
capacity = static_cast<sb4> (sizeof (OCIInterval*));
-
break;
}
case bind::interval_ds:
{
- interval_ds* ids (static_cast<interval_ds*> (b->buffer));
-
- if (ids->descriptor == 0)
+ for (size_t i (0); i != batch; ++i)
{
- void* d (0);
- r = OCIDescriptorAlloc (env,
- &d,
- OCI_DTYPE_INTERVAL_DS,
- 0,
- 0);
+ interval_ds* ids (
+ static_cast<interval_ds*> (offset (b->buffer, i, skip)));
- if (r != OCI_SUCCESS)
- throw invalid_oci_handle ();
+ void* pd (0); // Pointer to descriptor.
- if (ids->flags & descriptor_cache)
+ if (ids->descriptor == 0)
{
- ids->descriptor = static_cast<OCIInterval*> (d);
- ids->environment = env;
- ids->error = err;
- }
-
- // If the interval_ds instance is not responsible for the
- // descriptor, then we have to arrange to have it freed
- // using the unbind machinery.
- //
- if ((ids->flags & descriptor_free) == 0)
- {
- unbind& u (udata_[usize_++]);
-
- u.type = bind::interval_ds;
- u.bind = (ids->flags & descriptor_cache) ? b : 0;
- u.value = d;
- value = &u.value;
+ void* d (0);
+ r = OCIDescriptorAlloc (env,
+ &d,
+ OCI_DTYPE_INTERVAL_DS,
+ 0,
+ 0);
+
+ if (r != OCI_SUCCESS)
+ throw invalid_oci_handle ();
+
+ if (ids->flags & descriptor_cache)
+ {
+ ids->descriptor = static_cast<OCIInterval*> (d);
+ ids->environment = env;
+ ids->error = err;
+ }
+
+ // If the interval_ds instance is not responsible for the
+ // descriptor, then we have to arrange to have it freed
+ // using the unbind machinery.
+ //
+ if ((ids->flags & descriptor_free) == 0)
+ {
+ unbind& u (udata_[usize_++]);
+
+ u.type = bind::interval_ds;
+ u.bind = (ids->flags & descriptor_cache) ? b : 0;
+ u.value = d;
+ pd = &u.value;
+ }
+ else
+ pd = &ids->descriptor;
+
+ // Initialize the descriptor from the cached data.
+ //
+ if (b->indicator == 0 || *b->indicator != -1)
+ r = OCIIntervalSetDaySecond (env,
+ err,
+ ids->day_,
+ ids->hour_,
+ ids->minute_,
+ ids->second_,
+ ids->nanosecond_,
+ static_cast<OCIInterval*> (d));
+
+ if (r != OCI_SUCCESS)
+ translate_error (err, r);
}
else
- value = &ids->descriptor;
-
- // Initialize the descriptor from the cached data.
- //
- if (b->indicator == 0 || *b->indicator != -1)
- r = OCIIntervalSetDaySecond (env,
- err,
- ids->day_,
- ids->hour_,
- ids->minute_,
- ids->second_,
- ids->nanosecond_,
- static_cast<OCIInterval*> (d));
+ pd = &ids->descriptor;
- if (r != OCI_SUCCESS)
- translate_error (err, r);
+ if (i == 0)
+ value = pd;
}
- else
- value = &ids->descriptor;
capacity = static_cast<sb4> (sizeof (OCIInterval*));
-
break;
}
case bind::blob:
@@ -589,7 +634,11 @@ namespace odb
// However, in Oracle, LOBs cannot be used in queries so we can
// make an exception here.
//
- l->buffer = &lob_buffer;
+ for (size_t i (0); i != batch;)
+ {
+ l->buffer = &lob_buffer;
+ l = static_cast<lob*> (offset (b->buffer, ++i, skip));
+ }
}
assert (callback);
@@ -603,6 +652,11 @@ namespace odb
//
capacity = 4096;
+ // Store skip in capacity so that the callback can offset the
+ // values based on the iteration number.
+ //
+ b->capacity = static_cast<ub4> (skip);
+
break;
}
default:
@@ -686,6 +740,23 @@ namespace odb
if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
translate_error (err, r);
}
+
+ // Set array information if we have a batch.
+ //
+ if (batch != 1)
+ {
+ ub4 s (static_cast<ub4> (skip));
+
+ r = OCIBindArrayOfStruct (h,
+ err,
+ (value != 0 ? s : 0), // value
+ (b->indicator != 0 ? s : 0), // indicator
+ (size != 0 ? s : 0), // length
+ 0); // return code
+
+ if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
+ translate_error (err, r);
+ }
}
return i;
@@ -1175,6 +1246,206 @@ namespace odb
}
//
+ // bulk_statement
+ //
+
+ bulk_statement::
+ ~bulk_statement () {}
+
+ sword bulk_statement::
+ execute (size_t n, multiple_exceptions* mex, sb4 ignore_code)
+ {
+ {
+ odb::tracer* t;
+ if ((t = conn_.transaction_tracer ()) ||
+ (t = conn_.tracer ()) ||
+ (t = conn_.database ().tracer ()))
+ t->execute (conn_, *this);
+ }
+
+ mex_ = mex;
+
+ OCIError* err (conn_.error_handle ());
+
+ // We use OCI_BATCH_ERRORS for n == 1 in order to get the batch
+ // error reporting even for a single parameter set. This makes
+ // it easier to populate mex since otherwise we would have two
+ // cases to worry about: batch and non-batch (statement fails
+ // as a whole).
+ //
+ sword r (OCIStmtExecute (conn_.handle (),
+ stmt_,
+ err,
+ static_cast<ub4> (n),
+ 0,
+ 0,
+ 0,
+ status_ == 0 ? OCI_DEFAULT : OCI_BATCH_ERRORS));
+
+ // If the statement failed as a whole, assume no parameter sets
+ // were attempted in case of a batch. Otherwise, in the batch
+ // errors mode, all the sets are always attempted (let's hope
+ // this is actually true).
+ //
+ i_ = 0;
+ n_ = (r == OCI_ERROR || r == OCI_INVALID_HANDLE
+ ? (status_ == 0 ? 1 : 0)
+ : n);
+
+ if (mex_ != 0)
+ {
+ mex_->current (i_);
+ mex_->attempted (n_);
+ }
+
+ if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
+ {
+ if (mex_ != 0)
+ mex_->fatal (true); // An incomplete batch is always fatal.
+
+ return r;
+ }
+
+ // Initialize the batch status array and extract error information
+ // for failed parameter sets.
+ //
+ if (status_ != 0)
+ {
+ sword r; // Our own return code.
+
+ // Clear the status array.
+ //
+ memset (status_, 0, n * sizeof (status_[0]));
+
+ if (err1_ == 0)
+ {
+ OCIError* e (0);
+ r = OCIHandleAlloc (conn_.database ().environment (),
+ reinterpret_cast<void**> (&e),
+ OCI_HTYPE_ERROR,
+ 0,
+ 0);
+
+ if (r != OCI_SUCCESS)
+ throw invalid_oci_handle ();
+
+ err1_.reset (e);
+ }
+
+ ub4 errors;
+ r = OCIAttrGet (stmt_,
+ OCI_HTYPE_STMT,
+ &errors,
+ 0,
+ OCI_ATTR_NUM_DML_ERRORS,
+ err1_);
+
+ if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
+ translate_error (err1_, r);
+
+ errors_ = errors;
+
+ if (errors != 0)
+ {
+ auto_handle<OCIError> err2;
+
+ {
+ OCIError* e (0);
+ r = OCIHandleAlloc (conn_.database ().environment (),
+ reinterpret_cast<void**> (&e),
+ OCI_HTYPE_ERROR,
+ 0,
+ 0);
+
+ if (r != OCI_SUCCESS)
+ throw invalid_oci_handle ();
+
+ err2.reset (e);
+ }
+
+ for (ub4 i (0); i != errors; ++i)
+ {
+ {
+ OCIError* tmp (err2);
+ r = OCIParamGet (err, // from
+ OCI_HTYPE_ERROR,
+ err1_, // diagnostics
+ reinterpret_cast<void**> (&tmp), // to
+ i);
+
+ if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
+ translate_error (err1_, r);
+ }
+
+ ub4 row;
+ r = OCIAttrGet (err2,
+ OCI_HTYPE_ERROR,
+ &row,
+ 0,
+ OCI_ATTR_DML_ROW_OFFSET,
+ err1_);
+
+ if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
+ translate_error (err1_, r);
+
+ OCIErrorGet (err2, 1, 0, &status_[row], 0, 0, OCI_HTYPE_ERROR);
+
+ if (status_[row] != ignore_code)
+ translate_error (err2, OCI_ERROR, &conn_, row, mex_);
+ }
+ }
+ }
+
+ return r;
+ }
+
+ unsigned long long bulk_statement::
+ affected (bool unique)
+ {
+ unsigned long long rows;
+ {
+ ub4 n (0);
+ OCIError* err (conn_.error_handle ());
+ sword r (OCIAttrGet (stmt_,
+ OCI_HTYPE_STMT,
+ &n,
+ 0,
+ OCI_ATTR_ROW_COUNT,
+ err));
+
+ if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
+ translate_error (err, r);
+
+ rows = static_cast<unsigned long long> (n);
+ }
+
+ if (n_ > 1) // Batch.
+ {
+ if (rows != 0) // Some rows did get affected.
+ {
+ // Subtract the parameter sets that failed since they haven't
+ // affected any rows.
+ //
+ size_t p (n_ - errors_);
+
+ if (p > 1) // True batch.
+ {
+ if (unique) // Each can affect 0 or 1 row.
+ {
+ rows = (p == static_cast<size_t> (rows)
+ ? 1
+ : result_unknown);
+ }
+ else
+ rows = result_unknown;
+ }
+ }
+ }
+
+ return rows;
+ }
+
+ //
// generic_statement
//
@@ -1559,22 +1830,25 @@ namespace odb
extern "C" sb4
odb_oracle_returning_in (void* context,
OCIBind*, // bind
- ub4, // iter
+ ub4 it, // iter
ub4, // index
void** buffer,
ub4* size,
ub1* piece,
void** indicator)
{
- typedef insert_statement::id_bind_type bind;
-
- bind& b (*static_cast<bind*> (context));
+ binding& ret (*static_cast<insert_statement*> (context)->ret_);
+ // Offset the data based on the current iteration and skip size.
+ // The id is always first.
+ //
*buffer = 0;
*size = 0;
*piece = OCI_ONE_PIECE;
- b.indicator = -1;
- *indicator = &b.indicator;
+
+ sb2* ind (offset (ret.bind[0].indicator, it, ret.skip));
+ *ind = -1;
+ *indicator = ind;
return OCI_CONTINUE;
}
@@ -1582,30 +1856,56 @@ namespace odb
extern "C" sb4
odb_oracle_returning_out (void* context,
OCIBind*, // bind
- ub4, // iter
+ ub4 it, // iter
ub4, // index
void** buffer,
ub4** size,
- ub1*, // piece
+ ub1* piece,
void** indicator,
ub2** rcode)
{
- typedef insert_statement::id_bind_type bind;
+ insert_statement& st (*static_cast<insert_statement*> (context));
+ bind& b (st.ret_->bind[0]); // The id is always first.
+ size_t skip (st.ret_->skip);
- bind& b (*static_cast<bind*> (context));
+ // Offset the data based on the current iteration and skip size.
+ //
+ *buffer = offset (b.buffer, it, skip);
-#if (OCI_MAJOR_VERSION == 11 && OCI_MINOR_VERSION >=2) \
- || OCI_MAJOR_VERSION > 11
- *buffer = &b.id.integer;
- **size = sizeof (unsigned long long);
-#else
- *buffer = b.id.number.buffer;
- *size = &b.id.number.size;
- b.id.number.size = 21;
-#endif
+ if (b.type == bind::number)
+ {
+ // So the straightforward way to handle this would have been to
+ // set size to the properly offset pointer to b.size, just like
+ // we do for the buffer and indicator. The problem is that in
+ // OCI size is ub2 everywhere except in the *Dynamic() callbacks.
+ // Here it is expected to be ub4 and, as a result, we cannot use
+ // our ub2 size that we use throughout (I know you are tempted
+ // to just cast ub2* to ub4* and forget about this mess, but,
+ // trust me, this won't end up well).
+ //
+ // So what we will do instead is this: have a temporary ub4 buffer
+ // that we return to OCI so that it can store the size for us. But
+ // the callback can be called multiple times (batch operations) so
+ // on each subsequent call we will have to save the size from the
+ // previous call into our ub2 array. We will also have to handle
+ // the last extracted size after OCIStmtExecute() below. Thanks,
+ // Oracle!
+ //
+ if (st.ret_prev_ != 0)
+ *st.ret_prev_ = static_cast<ub2> (st.ret_size_);
+
+ st.ret_prev_ = offset (b.size, it, skip);
+ *size = &st.ret_size_;
+ }
- *indicator = &b.indicator;
+ // For some reason we have to set the out size to the (presumably)
+ // maximum buffer size.
+ //
+ **size = b.capacity;
+
+ *indicator = offset (b.indicator, it, skip);
*rcode = 0;
+ *piece = OCI_ONE_PIECE;
return OCI_CONTINUE;
}
@@ -1620,12 +1920,14 @@ namespace odb
const string& text,
bool process,
binding& param,
- bool returning)
- : statement (conn,
- text, statement_insert,
- (process ? &param : 0), false)
+ binding* returning)
+ : bulk_statement (conn,
+ text, statement_insert,
+ (process ? &param : 0), false,
+ param.batch, param.status),
+ ret_ (returning)
{
- init (param, returning);
+ init (param);
}
insert_statement::
@@ -1633,37 +1935,44 @@ namespace odb
const char* text,
bool process,
binding& param,
- bool returning)
- : statement (conn,
- text, statement_insert,
- (process ? &param : 0), false)
+ binding* returning)
+ : bulk_statement (conn,
+ text, statement_insert,
+ (process ? &param : 0), false,
+ param.batch, param.status),
+ ret_ (returning)
{
- init (param, returning);
+ init (param);
}
void insert_statement::
- init (binding& param, bool returning)
+ init (binding& param)
{
- ub4 param_count (bind_param (param.bind, param.count));
-
- if (returning)
+ ub4 param_count (bind_param (param.bind, param.count,
+ param.batch, param.skip));
+ if (ret_ != 0)
{
OCIError* err (conn_.error_handle ());
OCIBind* h (0);
+ bind* b (ret_->bind);
+
+#if OCI_MAJOR_VERSION < 11 || \
+ (OCI_MAJOR_VERSION == 11 && OCI_MINOR_VERSION < 2)
+ // Assert if a 64 bit integer buffer type is provided and the OCI
+ // version is unable to implicitly convert the NUMBER binary data
+ // to the relevant type.
+ //
+ assert ((b->type != bind::integer && b->type != bind::uinteger) ||
+ b->capacity <= 4);
+#endif
sword r (OCIBindByPos (stmt_,
&h,
err,
param_count + 1,
0,
-#if (OCI_MAJOR_VERSION == 11 && OCI_MINOR_VERSION >=2) \
- || OCI_MAJOR_VERSION > 11
- sizeof (unsigned long long),
- SQLT_UIN,
-#else
- 21,
- SQLT_NUM,
-#endif
+ b->capacity,
+ param_sqlt_lookup[b->type],
0,
0,
0,
@@ -1676,9 +1985,9 @@ namespace odb
r = OCIBindDynamic (h,
err,
- &id_bind_,
+ this,
&odb_oracle_returning_in,
- &id_bind_,
+ this,
&odb_oracle_returning_out);
if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
@@ -1686,71 +1995,47 @@ namespace odb
}
}
- bool insert_statement::
- execute ()
+ size_t insert_statement::
+ execute (size_t n, multiple_exceptions* mex)
{
- {
- odb::tracer* t;
- if ((t = conn_.transaction_tracer ()) ||
- (t = conn_.tracer ()) ||
- (t = conn_.database ().tracer ()))
- t->execute (conn_, *this);
- }
-
OCIError* err (conn_.error_handle ());
- sword r (OCIStmtExecute (conn_.handle (),
- stmt_,
- err,
- 1,
- 0,
- 0,
- 0,
- OCI_DEFAULT));
+ if (ret_ != 0)
+ ret_prev_ = 0;
+
+ // Ignore ORA-00001 error code, see fetch() below for details.
+ //
+ sword r (bulk_statement::execute (n, mex, (ret_ == 0 ? 1 : 0)));
+ // Statement failed as a whole, assume no parameter sets were
+ // attempted in case of a batch.
+ //
if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
{
sb4 e;
OCIErrorGet (err, 1, 0, &e, 0, 0, OCI_HTYPE_ERROR);
+ fetch (r, e);
- // The Oracle error code ORA-00001 indicates unique constraint
- // violation, which covers more than just a duplicate primary key.
- // Unfortunately, there is nothing more precise that we can use.
- //
- if (e == 1)
- return false;
- else
- translate_error (conn_, r);
- }
+ if (result_) // If fetch() hasn't translated the error.
+ translate_error (err, r, &conn_, 0, mex_); // Can return.
- ub4 row_count (0);
- r = OCIAttrGet (stmt_,
- OCI_HTYPE_STMT,
- &row_count,
- 0,
- OCI_ATTR_ROW_COUNT,
- err);
-
- if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
- translate_error (err, r);
+ return n_;
+ }
- // The value of the OCI_ATTR_ROW_COUNT attribute associated with an
- // INSERT statment represents the number of rows inserted.
+ // Store the last returned id size (see odb_oracle_returning_out()
+ // for details).
//
- return row_count != 0;
- }
+ if (ret_ != 0 && ret_prev_ != 0)
+ *ret_prev_ = static_cast<ub2> (ret_size_);
- unsigned long long insert_statement::
- id ()
- {
-#if (OCI_MAJOR_VERSION == 11 && OCI_MINOR_VERSION >=2) \
- || OCI_MAJOR_VERSION > 11
- return id_bind_.id.integer;
-#else
- return details::number_to_uint64 (
- id_bind_.id.number.buffer,
- static_cast <std::size_t> (id_bind_.id.number.size));
-#endif
+ if (status_ == 0) // Non-batch mode.
+ fetch (OCI_SUCCESS, 0);
+ else
+ {
+ fetch (status_[i_] == 0 ? OCI_SUCCESS : OCI_ERROR, status_[i_]);
+ }
+
+ return n_;
}
//
@@ -1767,68 +2052,88 @@ namespace odb
const string& text,
bool process,
binding& param)
- : statement (conn,
- text, statement_update,
- (process ? &param : 0), false)
+ : bulk_statement (conn,
+ text, statement_update,
+ (process ? &param : 0), false,
+ param.batch, param.status),
+ unique_ (false)
{
+ assert (param.batch == 1); // Specify unique_hint explicitly.
+
if (!empty ())
- bind_param (param.bind, param.count);
+ bind_param (param.bind, param.count, param.batch, param.skip);
}
update_statement::
update_statement (connection_type& conn,
- const char* text,
+ const string& text,
+ bool unique,
bool process,
binding& param)
- : statement (conn,
- text, statement_update,
- (process ? &param : 0), false)
+ : bulk_statement (conn,
+ text, statement_update,
+ (process ? &param : 0), false,
+ param.batch, param.status),
+ unique_ (unique)
{
if (!empty ())
- bind_param (param.bind, param.count);
+ bind_param (param.bind, param.count, param.batch, param.skip);
}
- unsigned long long update_statement::
- execute ()
+ update_statement::
+ update_statement (connection_type& conn,
+ const char* text,
+ bool process,
+ binding& param)
+ : bulk_statement (conn,
+ text, statement_update,
+ (process ? &param : 0), false,
+ param.batch, param.status),
+ unique_ (false)
{
- {
- odb::tracer* t;
- if ((t = conn_.transaction_tracer ()) ||
- (t = conn_.tracer ()) ||
- (t = conn_.database ().tracer ()))
- t->execute (conn_, *this);
- }
-
- OCIError* err (conn_.error_handle ());
+ assert (param.batch == 1); // Specify unique_hint explicitly.
- sword r (OCIStmtExecute (conn_.handle (),
- stmt_,
- err,
- 1,
- 0,
- 0,
- 0,
- OCI_DEFAULT));
+ if (!empty ())
+ bind_param (param.bind, param.count, param.batch, param.skip);
+ }
- if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
- translate_error (conn_, r);
+ update_statement::
+ update_statement (connection_type& conn,
+ const char* text,
+ bool unique,
+ bool process,
+ binding& param)
+ : bulk_statement (conn,
+ text, statement_update,
+ (process ? &param : 0), false,
+ param.batch, param.status),
+ unique_ (unique)
+ {
+ if (!empty ())
+ bind_param (param.bind, param.count, param.batch, param.skip);
+ }
- ub4 row_count (0);
- r = OCIAttrGet (stmt_,
- OCI_HTYPE_STMT,
- &row_count,
- 0,
- OCI_ATTR_ROW_COUNT,
- err);
+ size_t update_statement::
+ execute (size_t n, multiple_exceptions* mex)
+ {
+ OCIError* err (conn_.error_handle ());
+ sword r (bulk_statement::execute (n, mex));
+ // Statement failed as a whole, assume no parameter sets were
+ // attempted in case of a batch.
+ //
if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
- translate_error (err, r);
+ {
+ translate_error (err, r, &conn_, 0, mex_); // Can return.
+ return n_;
+ }
- // The value of the OCI_ATTR_ROW_COUNT attribute associated with an
- // UPDATE statment represents the number of matching rows found. Zero
- // indicates no match.
+ // Figure out the affected (matched, not necessarily updated)
+ // row count.
//
- return static_cast<unsigned long long> (row_count);
+ result_ = affected (unique_);
+
+ return n_;
}
//
@@ -1844,61 +2149,78 @@ namespace odb
delete_statement (connection_type& conn,
const string& text,
binding& param)
- : statement (conn,
- text, statement_delete,
- 0, false)
+ : bulk_statement (conn,
+ text, statement_delete,
+ 0, false,
+ param.batch, param.status),
+ unique_ (false)
{
- bind_param (param.bind, param.count);
+ assert (param.batch == 1); // Specify unique_hint explicitly.
+ bind_param (param.bind, param.count, param.batch, param.skip);
+ }
+
+ delete_statement::
+ delete_statement (connection_type& conn,
+ const string& text,
+ bool unique,
+ binding& param)
+ : bulk_statement (conn,
+ text, statement_delete,
+ 0, false,
+ param.batch, param.status),
+ unique_ (unique)
+ {
+ bind_param (param.bind, param.count, param.batch, param.skip);
}
delete_statement::
delete_statement (connection_type& conn,
const char* text,
binding& param)
- : statement (conn,
- text, statement_delete,
- 0, false)
+ : bulk_statement (conn,
+ text, statement_delete,
+ 0, false,
+ param.batch, param.status),
+ unique_ (false)
{
- bind_param (param.bind, param.count);
+ assert (param.batch == 1); // Specify unique_hint explicitly.
+ bind_param (param.bind, param.count, param.batch, param.skip);
}
- unsigned long long delete_statement::
- execute ()
+ delete_statement::
+ delete_statement (connection_type& conn,
+ const char* text,
+ bool unique,
+ binding& param)
+ : bulk_statement (conn,
+ text, statement_delete,
+ 0, false,
+ param.batch, param.status),
+ unique_ (unique)
{
- {
- odb::tracer* t;
- if ((t = conn_.transaction_tracer ()) ||
- (t = conn_.tracer ()) ||
- (t = conn_.database ().tracer ()))
- t->execute (conn_, *this);
- }
+ bind_param (param.bind, param.count, param.batch, param.skip);
+ }
+ size_t delete_statement::
+ execute (size_t n, multiple_exceptions* mex)
+ {
+ sword r (bulk_statement::execute (n, mex));
OCIError* err (conn_.error_handle ());
- sword r (OCIStmtExecute (conn_.handle (),
- stmt_,
- err,
- 1,
- 0,
- 0,
- 0,
- OCI_DEFAULT));
-
+ // Statement failed as a whole, assume no parameter sets were
+ // attempted in case of a batch.
+ //
if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
- translate_error (conn_, r);
-
- ub4 row_count (0);
- r = OCIAttrGet (stmt_,
- OCI_HTYPE_STMT,
- &row_count,
- 0,
- OCI_ATTR_ROW_COUNT,
- err);
+ {
+ translate_error (err, r, &conn_, 0, mex_); // Can return.
+ return n_;
+ }
- if (r == OCI_ERROR || r == OCI_INVALID_HANDLE)
- translate_error (err, r);
+ // Figure out the affected row count.
+ //
+ result_ = affected (unique_);
- return static_cast<unsigned long long> (row_count);
+ return n_;
}
}
}
diff --git a/odb/oracle/statement.hxx b/odb/oracle/statement.hxx
index cf336c4..6a5d604 100644
--- a/odb/oracle/statement.hxx
+++ b/odb/oracle/statement.hxx
@@ -11,6 +11,7 @@
#include <cstddef> // std::size_t
#include <odb/statement.hxx>
+#include <odb/exceptions.hxx>
#include <odb/oracle/version.hxx>
#include <odb/oracle/forward.hxx>
@@ -96,7 +97,8 @@ namespace odb
// of columns bound.
//
ub4
- bind_param (bind*, std::size_t count);
+ bind_param (bind*, std::size_t count,
+ size_t batch = 1, std::size_t skip = 0);
// Bind results for this statement. This function must only be
// called once. Multiple calls to it will result in memory leaks
@@ -141,6 +143,50 @@ namespace odb
std::size_t usize_;
};
+ class LIBODB_ORACLE_EXPORT bulk_statement: public statement
+ {
+ public:
+ virtual
+ ~bulk_statement () = 0;
+
+ protected:
+ bulk_statement (connection_type&,
+ const std::string& text,
+ statement_kind,
+ const binding* process,
+ bool optimize,
+ std::size_t batch,
+ sb4* status);
+
+ bulk_statement (connection_type&,
+ const char* text,
+ statement_kind,
+ const binding* process,
+ bool optimize,
+ std::size_t batch,
+ sb4* status);
+
+ // Call OCIStmtExecute() and set up the batch tracking variables (see
+ // below). The ignore_code argument specifies optional error code that
+ // should not be treated as an error.
+ //
+ sword
+ execute (std::size_t n, multiple_exceptions*, sb4 ignore_code = 0);
+
+ static const unsigned long long result_unknown = ~0ULL;
+
+ unsigned long long
+ affected (bool unique);
+
+ protected:
+ auto_handle<OCIError> err1_;
+ sb4* status_; // Parameter sets status array.
+ std::size_t n_; // Actual batch size.
+ std::size_t i_; // Position in result.
+ std::size_t errors_; // Number of parameter sets that failed.
+ multiple_exceptions* mex_;
+ };
+
class LIBODB_ORACLE_EXPORT generic_statement: public statement
{
public:
@@ -251,7 +297,7 @@ namespace odb
select_statement& s_;
};
- class LIBODB_ORACLE_EXPORT insert_statement: public statement
+ class LIBODB_ORACLE_EXPORT insert_statement: public bulk_statement
{
public:
virtual
@@ -261,106 +307,222 @@ namespace odb
const std::string& text,
bool process_text,
binding& param,
- bool returning);
+ binding* returning);
insert_statement (connection_type& conn,
const char* text,
bool process_text,
binding& param,
- bool returning);
+ binding* returning);
- // Return true if successful and false if the row is a duplicate. All
- // other errors are reported by throwing exceptions.
+ // Return the number of parameter sets (out of n) that were attempted.
+ //
+ std::size_t
+ execute (std::size_t n, multiple_exceptions& mex)
+ {
+ return execute (n, &mex);
+ }
+
+ // Return true if successful and false if this row is a duplicate.
+ // All other errors are reported via exceptions.
//
bool
- execute ();
+ result (std::size_t i);
- unsigned long long
- id ();
+ bool
+ execute ()
+ {
+ execute (1, 0);
+ return result (0);
+ }
private:
insert_statement (const insert_statement&);
insert_statement& operator= (const insert_statement&);
- // Only OCI versions 11.2 and greater support conversion of the internal
- // Oracle type NUMBER to an external 64-bit integer type. If we detect
- // version 11.2 or greater we provide an unsigned long long image.
- // Otherwise, we revert to using a NUMBER image and manipulate it using
- // the custom conversion algorithms found in details/number.hxx.
- //
- public:
- struct id_bind_type
- {
- union
- {
- struct
- {
- char buffer[21];
- ub4 size;
- } number;
-
- unsigned long long integer;
- } id;
-
- sb2 indicator;
- };
-
private:
void
- init (binding& param, bool returning);
+ init (binding& param);
+
+ std::size_t
+ execute (std::size_t, multiple_exceptions*);
+
+ void
+ fetch (sword r, sb4 code);
+
+ public: // For odb_oracle_returning_*().
+ binding* ret_;
+ ub4 ret_size_; // You don't want to know (see statement.cxx).
+ ub2* ret_prev_;
private:
- id_bind_type id_bind_;
+ bool result_;
};
- class LIBODB_ORACLE_EXPORT update_statement: public statement
+ class LIBODB_ORACLE_EXPORT update_statement: public bulk_statement
{
public:
virtual
~update_statement ();
+ // OCI does not expose individual affected row counts for batch
+ // operations. Instead, it adds them all up and returns a single
+ // count. This is bad news for us.
+ //
+ // In case of updating by primary key (the affected row count is
+ // either 1 or 0), we can recognize the presumably successful case
+ // where the total affected row count is equal to the batch size
+ // (we can also recognize the "all unsuccessful" case where the
+ // total affected row count is 0). The unique_hint argument in the
+ // constructors below indicates whether this is a "0 or 1" UPDATE
+ // statement.
+ //
+ // In all other situations (provided this is a batch), the result()
+ // function below returns the special result_unknown value.
+ //
+ update_statement (connection_type& conn,
+ const std::string& text,
+ bool process_text,
+ binding& param);
+
update_statement (connection_type& conn,
const std::string& text,
+ bool unique_hint,
+ bool process_text,
+ binding& param);
+
+ update_statement (connection_type& conn,
+ const char* text,
bool process_text,
binding& param);
update_statement (connection_type& conn,
const char* text,
+ bool unique_hint,
bool process_text,
binding& param);
+ // Return the number of parameter sets (out of n) that were attempted.
+ //
+ std::size_t
+ execute (std::size_t n, multiple_exceptions& mex)
+ {
+ return execute (n, &mex);
+ }
+
+ // Return the number of rows affected (deleted) by the parameter
+ // set. If this is a batch (n > 1 in execute() call above) and it
+ // is impossible to determine the affected row count for each
+ // parameter set, then this function returns result_unknown. All
+ // other errors are reported by throwing exceptions.
+ //
+ using bulk_statement::result_unknown;
+
unsigned long long
- execute ();
+ result (std::size_t i);
+
+ unsigned long long
+ execute ()
+ {
+ execute (1, 0);
+ return result (0);
+ }
private:
update_statement (const update_statement&);
update_statement& operator= (const update_statement&);
+
+ private:
+ std::size_t
+ execute (std::size_t, multiple_exceptions*);
+
+ private:
+ bool unique_;
+ unsigned long long result_;
};
- class LIBODB_ORACLE_EXPORT delete_statement: public statement
+ class LIBODB_ORACLE_EXPORT delete_statement: public bulk_statement
{
public:
virtual
~delete_statement ();
+ // OCI does not expose individual affected row counts for batch
+ // operations. Instead, it adds them all up and returns a single
+ // count. This is bad news for us.
+ //
+ // In case of deleting by primary key (the affected row count is
+ // either 1 or 0), we can recognize the presumably successful case
+ // where the total affected row count is equal to the batch size
+ // (we can also recognize the "all unsuccessful" case where the
+ // total affected row count is 0). The unique_hint argument in the
+ // constructors below indicates whether this is a "0 or 1" DELETE
+ // statement.
+ //
+ // In all other situations (provided this is a batch), the result()
+ // function below returns the special result_unknown value.
+ //
+ delete_statement (connection_type& conn,
+ const std::string& text,
+ binding& param);
+
delete_statement (connection_type& conn,
const std::string& text,
+ bool unique_hint,
+ binding& param);
+
+ delete_statement (connection_type& conn,
+ const char* text,
binding& param);
delete_statement (connection_type& conn,
const char* text,
+ bool unique_hint,
binding& param);
+ // Return the number of parameter sets (out of n) that were attempted.
+ //
+ std::size_t
+ execute (std::size_t n, multiple_exceptions& mex)
+ {
+ return execute (n, &mex);
+ }
+
+ // Return the number of rows affected (deleted) by the parameter
+ // set. If this is a batch (n > 1 in execute() call above) and it
+ // is impossible to determine the affected row count for each
+ // parameter set, then this function returns result_unknown. All
+ // other errors are reported by throwing exceptions.
+ //
+ using bulk_statement::result_unknown;
+
unsigned long long
- execute ();
+ result (std::size_t i);
+
+ unsigned long long
+ execute ()
+ {
+ execute (1, 0);
+ return result (0);
+ }
private:
delete_statement (const delete_statement&);
delete_statement& operator= (const delete_statement&);
+
+ private:
+ std::size_t
+ execute (std::size_t, multiple_exceptions*);
+
+ private:
+ bool unique_;
+ unsigned long long result_;
};
}
}
+#include <odb/oracle/statement.ixx>
+
#include <odb/post.hxx>
#endif // ODB_ORACLE_STATEMENT_HXX
diff --git a/odb/oracle/statement.ixx b/odb/oracle/statement.ixx
new file mode 100644
index 0000000..0ce76c6
--- /dev/null
+++ b/odb/oracle/statement.ixx
@@ -0,0 +1,100 @@
+// file : odb/oracle/statement.ixx
+// copyright : Copyright (c) 2005-2013 Code Synthesis Tools CC
+// license : ODB NCUEL; see accompanying LICENSE file
+
+using namespace std;
+
+namespace odb
+{
+ namespace oracle
+ {
+ // bulk_statement
+ //
+ inline bulk_statement::
+ bulk_statement (connection_type& c,
+ const std::string& text,
+ statement_kind k,
+ const binding* process,
+ bool optimize,
+ std::size_t batch,
+ sb4* status)
+ : statement (c, text, k, process, optimize),
+ status_ (batch == 1 ? 0 : status)
+ {
+ }
+
+ inline bulk_statement::
+ bulk_statement (connection_type& c,
+ const char* text,
+ statement_kind k,
+ const binding* process,
+ bool optimize,
+ std::size_t batch,
+ sb4* status)
+ : statement (c, text, k, process, optimize),
+ status_ (batch == 1 ? 0 : status)
+ {
+ }
+
+ // insert_statement
+ //
+ inline void insert_statement::
+ fetch (sword r, sb4 code)
+ {
+ result_ = true;
+
+ if (r != 0 /*OCI_SUCCESS*/)
+ {
+ // An auto-assigned object id should never cause a duplicate primary
+ // key.
+ //
+ if (ret_ == 0)
+ {
+ // The Oracle error code ORA-00001 indicates unique constraint
+ // violation, which covers more than just a duplicate primary key.
+ // Unfortunately, there is nothing more precise that we can use.
+ //
+ if (code == 1)
+ result_ = false;
+ }
+ }
+ }
+
+ inline bool insert_statement::
+ result (std::size_t i)
+ {
+ // Get to the next parameter set if necessary.
+ //
+ if (i != i_)
+ {
+ mex_->current (++i_); // mex cannot be NULL since this is a batch.
+ fetch (status_[i_] == 0 ? 0 /*OCI_SUCCESS*/ : -1 /*OCI_ERROR*/,
+ status_[i_]);
+ }
+
+ return result_;
+ }
+
+ // update_statement
+ //
+ inline unsigned long long update_statement::
+ result (std::size_t i)
+ {
+ if (i != i_)
+ mex_->current (++i_); // mex cannot be NULL since this is a batch.
+
+ return result_;
+ }
+
+ // delete_statement
+ //
+ inline unsigned long long delete_statement::
+ result (std::size_t i)
+ {
+ if (i != i_)
+ mex_->current (++i_); // mex cannot be NULL since this is a batch.
+
+ return result_;
+ }
+ }
+}