From b39e670403bfdff6870d9b9c7b075230e84c27cf Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 14 Aug 2014 09:37:06 +0200 Subject: Implement bulk database operation support for Oracle and SQL Server --- odb/oracle/binding.hxx | 22 +- odb/oracle/container-statements.hxx | 2 +- odb/oracle/database.hxx | 31 + odb/oracle/database.ixx | 35 + odb/oracle/error.cxx | 21 +- odb/oracle/error.hxx | 12 +- odb/oracle/exceptions.cxx | 24 + odb/oracle/exceptions.hxx | 12 + odb/oracle/no-id-object-statements.hxx | 9 +- odb/oracle/no-id-object-statements.txx | 10 +- odb/oracle/oracle-types.hxx | 3 +- odb/oracle/polymorphic-object-statements.hxx | 8 +- odb/oracle/section-statements.hxx | 3 +- odb/oracle/section-statements.txx | 2 +- odb/oracle/simple-object-statements.hxx | 69 +- odb/oracle/simple-object-statements.txx | 25 +- odb/oracle/statement.cxx | 976 ++++++++++++++++++--------- odb/oracle/statement.hxx | 238 +++++-- odb/oracle/statement.ixx | 100 +++ 19 files changed, 1179 insertions(+), 423 deletions(-) create mode 100644 odb/oracle/statement.ixx 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 #include +#include #include @@ -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 object_traits::id_type + persist (const T& object); + + template + typename object_traits::id_type persist (T* obj_ptr); template class P> @@ -117,6 +121,13 @@ namespace odb typename object_traits::id_type persist (const typename object_traits::pointer_type& obj_ptr); + // Bulk persist. Can be a range of references or pointers (including + // smart pointers) to objects. + // + template + void + persist (I begin, I end, bool continue_failed = true); + // Load an object. Throw object_not_persistent if not found. // template @@ -203,6 +214,13 @@ namespace odb void update (const typename object_traits::pointer_type& obj_ptr); + // Bulk update. Can be a range of references or pointers (including + // smart pointers) to objects. + // + template + 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::pointer_type& obj_ptr); + // Bulk erase. + // + template + 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 + void + erase (I obj_begin, I obj_end, bool continue_failed = true); + // Erase multiple objects matching a query predicate. // template 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 inline typename object_traits::id_type database:: + persist (const T& obj) + { + return persist_ (obj); + } + + template + inline typename object_traits::id_type database:: persist (T* p) { typedef typename object_traits::pointer_type object_pointer; @@ -93,6 +100,13 @@ namespace odb return persist_ (pobj); } + template + inline void database:: + persist (I b, I e, bool cont) + { + persist_ (b, e, cont); + } + template inline typename object_traits::pointer_type database:: load (const typename object_traits::id_type& id) @@ -254,6 +268,13 @@ namespace odb update_ (pobj); } + template + inline void database:: + update (I b, I e, bool cont) + { + update_ (b, e, cont); + } + template inline void database:: update (const T& obj, const section& s) @@ -343,6 +364,20 @@ namespace odb erase_ (pobj); } + template + inline void database:: + erase (I idb, I ide, bool cont) + { + erase_id_ (idb, ide, cont); + } + + template + inline void database:: + erase (I ob, I oe, bool cont) + { + erase_object_ (ob, oe, cont); + } + template 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 +#include // std::size_t + #include -#include +#include // connection, multiple_exceptions #include + #include 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_; + extra_statement_cache_ptr 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 section_statements:: 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 + template 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 - void extra_statement_cache_ptr:: - allocate (connection_type* c, image_type* im, binding* id, binding* idv) + template + void extra_statement_cache_ptr:: + 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::allocate; + p_ = new T (*c, *im, *idim, *id, *idv); + deleter_ = &extra_statement_cache_ptr::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 friend class polymorphic_derived_object_statements; - extra_statement_cache_ptr - extra_statement_cache_; + extra_statement_cache_ptr 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 -#include // std::strlen +#include // std::strlen, std::memset #include #include @@ -70,10 +70,18 @@ namespace odb SQLT_CLOB // bind::nclob }; + template + inline T* + offset (T* base, size_t count, size_t size) + { + return reinterpret_cast ( + reinterpret_cast (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 (context)); - lob* l (static_cast (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 (offset (b.buffer, it, b.capacity))); + lob_callback* cb ( + static_cast (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 (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 (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 (offset (b->buffer, i, skip))); - if (r != OCI_SUCCESS) - throw invalid_oci_handle (); - - if (dt->flags & descriptor_cache) - { - dt->descriptor = static_cast (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 (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 (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 (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 (sizeof (OCIDateTime*)); - break; } case bind::interval_ym: { - interval_ym* iym (static_cast (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 (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 (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 (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 (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 (d)); + pd = &iym->descriptor; - if (r != OCI_SUCCESS) - translate_error (err, r); + if (i == 0) + value = pd; } - else - value = &iym->descriptor; capacity = static_cast (sizeof (OCIInterval*)); - break; } case bind::interval_ds: { - interval_ds* ids (static_cast (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 (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 (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 (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 (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 (d)); + pd = &ids->descriptor; - if (r != OCI_SUCCESS) - translate_error (err, r); + if (i == 0) + value = pd; } - else - value = &ids->descriptor; capacity = static_cast (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 (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 (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 (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 (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 (&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 err2; + + { + OCIError* e (0); + r = OCIHandleAlloc (conn_.database ().environment (), + reinterpret_cast (&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 (&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 (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 (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 (context)); + binding& ret (*static_cast (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 (context)); + bind& b (st.ret_->bind[0]); // The id is always first. + size_t skip (st.ret_->skip); - bind& b (*static_cast (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 (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 ? ¶m : 0), false) + binding* returning) + : bulk_statement (conn, + text, statement_insert, + (process ? ¶m : 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 ? ¶m : 0), false) + binding* returning) + : bulk_statement (conn, + text, statement_insert, + (process ? ¶m : 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 (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 (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 ? ¶m : 0), false) + : bulk_statement (conn, + text, statement_update, + (process ? ¶m : 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 ? ¶m : 0), false) + : bulk_statement (conn, + text, statement_update, + (process ? ¶m : 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 ? ¶m : 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 ? ¶m : 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 (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 (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 // std::size_t #include +#include #include #include @@ -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 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 + #include #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_; + } + } +} -- cgit v1.1