From ee99d2c795d9b6afc263f7ae5c15a62abd258e4e Mon Sep 17 00:00:00 2001 From: Constantin Michael Date: Fri, 16 Sep 2011 09:32:57 +0200 Subject: Corrections to LOB parameter and results callback implementation --- odb/oracle/oracle-types.hxx | 74 +++++++------- odb/oracle/statement.cxx | 233 +++++++++++++++++++++----------------------- odb/oracle/statement.hxx | 22 +++-- 3 files changed, 161 insertions(+), 168 deletions(-) (limited to 'odb') diff --git a/odb/oracle/oracle-types.hxx b/odb/oracle/oracle-types.hxx index 4d347d0..45485c2 100644 --- a/odb/oracle/oracle-types.hxx +++ b/odb/oracle/oracle-types.hxx @@ -15,6 +15,41 @@ namespace odb { namespace oracle { + enum chunk_position + { + one_chunk, + first_chunk, + next_chunk, + last_chunk + }; + + // Callback function signature used to specify LOB parameters that are + // passed to the database. If false is returned from the callback, + // statement execution is aborted. + // + typedef bool (*param_callback_type) ( + void* context, // [in] The user context. + ub4* position_context, // [in] A positional context. A callback is free + // to use this to track positional information. + void** buffer, // [out] On return, a pointer to a buffer + // containing parameter data. + ub4* size, // [out] The parameter data size in bytes. + chunk_position*, // [out] The position of the chunk of data in + // buffer. + void* temp_buffer, // [in] A temporary buffer that may be used if + // required. The buffer argument should specify + // this buffer on return if it is used. + ub4 capacity); // [in] The temporary buffer length in bytes. + + // Callback function signature used to specify LOB values returned from + // the database. If false is returned, database_exception is thrown. + // + typedef bool (*result_callback_type) ( + void* context, // [in] The user context. + void* buffer, // [in] A buffer containing the result data. + ub4 size, // [in] The result data length in bytes. + chunk_position); // [in] The position of this chunk. + struct bind { ub2 type; // The type stored by buffer. This must be an external @@ -22,53 +57,20 @@ namespace odb void* buffer; // Data buffer pointer. ub2* size; // The number of bytes in buffer. When parameter // callbacks are in use, this is interpreted as a ub4* - // indicating the current position. When 'type' - // specifies a LOB type, this is interpreted as an + // indicating the current position. When result + // callbacks are in use, this is interpreted as an // OCILobLocator*. ub4 capacity; // The maximum number of bytes that can be stored in // buffer. sb2* indicator; // Pointer to an OCI indicator variable. - enum piece - { - whole, - first, - next, - last - }; - - // Callback function signature used to specify LOB parameters that are - // passed to the database. - // - typedef bool (*param_callback_type) ( - void* context, // [in/out] The user context. - void** buffer, // [out] On return, a pointer to a buffer containing - // parameter data. - ub4* size, // [out] The parameter data length in bytes. - ub4* position, // [in/out] A position context. This value remains - // unchanged between callback invocations. - piece*, // [out] The piece type for this segment of data. - void* temp_buffer, // [in] A temporary buffer that may be used if - // required. The 'buffer' parameter should specify - // this buffer on return if it is used. - ub4 capacity); // [in] The temporary buffer length in bytes. - - // Callback function signature used to specify LOB values returned from - // the database. - // - typedef bool (*result_callback_type) ( - void* context, // [in/out] The user context. - void* buffer, // [in] A buffer containing the result data. - ub4 size, // [in] The result data length in bytes. - piece); // [in] The piece type for this segment of data. - union { param_callback_type param; result_callback_type result; } callback; - // This pointer is provided to the user through the 'context' parameter + // This pointer is provided to the user through the context argument // in both parameter and result callback functions. // void* callback_context; diff --git a/odb/oracle/statement.cxx b/odb/oracle/statement.cxx index 32b015a..70859a4 100644 --- a/odb/oracle/statement.cxx +++ b/odb/oracle/statement.cxx @@ -13,8 +13,9 @@ #include #include #include -#include #include +#include +#include using namespace std; @@ -28,52 +29,48 @@ namespace odb ub4, // iteration ub4, // index void** buffer, - ub4* len, + ub4* size, ub1* piece, void** indicator) { bind& b (*static_cast (context)); - // Only callback to user if the parameter is not NULL. + // Only call callback if the parameter is not NULL. // if (*b.indicator != -1) { - bind::piece p; - + chunk_position pos; if (!(*b.callback.param) (&b.callback_context, - buffer, - len, reinterpret_cast (b.size), - &p, + buffer, + size, + &pos, b.buffer, b.capacity)) return OCI_ERROR; - switch (p) + switch (pos) { - case bind::whole: + case one_chunk: { *piece = OCI_ONE_PIECE; break; } - case bind::first: + case first_chunk: { *piece = OCI_FIRST_PIECE; break; } - case bind::next: + case next_chunk: { *piece = OCI_NEXT_PIECE; break; } - case bind::last: + case last_chunk: { *piece = OCI_LAST_PIECE; break; } - default: - assert (0); - return OCI_ERROR; } } else @@ -117,17 +114,13 @@ namespace odb OCIError* err (conn_.error_handle ()); // The parameter position in OCIBindByPos is specified as a 1-based - // index. Convert 'o' to a 1-based offset. + // index. Convert o to a 1-based offset. // ++o; for (size_t e (o + c); o < e; ++c, ++b) { - bool callback ((b->type == SQLT_BLOB || b->type == SQLT_CLOB) && - b->callback.param != 0); - OCIBind* h (0); - sword r (OCIBindByPos (stmt_, &h, err, @@ -140,12 +133,13 @@ namespace odb 0, 0, 0, - callback ? OCI_DATA_AT_EXEC : OCI_DEFAULT)); + b->callback.param != 0 ? + OCI_DATA_AT_EXEC : OCI_DEFAULT)); if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) translate_error (err, r); - if (callback) + if (b->callback.param != 0) { r = OCIBindDynamic (h, err, b, ¶m_callback_proxy, 0, 0); @@ -162,9 +156,9 @@ namespace odb OCIError* err (conn_.error_handle ()); - // The parameter position in OCIDefineByPos is specified as a 1-based - // index. - // + result_binds_ = b; + count_ = c; + for (size_t i (1); i <= c; ++i, ++b) { if (b->type == SQLT_BLOB || b->type == SQLT_CLOB) @@ -185,10 +179,10 @@ namespace odb if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) translate_error (err, r); - // @@ LOB prefetching is only supported in OCI version 11.1 and - // greater and in Oracle server 11.1 and greater. If this code is - // called against a pre 11.1 server, the call to OCIAttrSet will - // return an error code. + // LOB prefetching is only supported in OCI version 11.1 and greater + // and in Oracle server 11.1 and greater. If this code is called + // against a pre 11.1 server, the call to OCIAttrSet will return an + // error code. // #if (OCI_MAJOR_VERSION == 11 && OCI_MINOR_VERSION >= 1) \ || OCI_MAJOR_VERSION > 11 @@ -204,7 +198,7 @@ namespace odb if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) translate_error (err, r); - bool b (true); + boolean b (true); r = OCIAttrSet (h, OCI_HTYPE_DEFINE, &b, @@ -238,6 +232,81 @@ namespace odb } } + void statement:: + stream_result_lobs () + { + OCIError* err (conn_.error_handle()); + + for (size_t i (0); i < count_; ++i) + { + bind& b (result_binds_[i]); + + // Only stream if the bind specifies a LOB type, and the LOB value is + // not NULL, and a result callback has been provided. + // + if ((b.type == SQLT_BLOB || b.type == SQLT_CLOB) && + *b.indicator != -1 && + b.callback.result != 0) + { + // If b.capacity is 0, we will be stuck in an infinite loop. + // + assert (b.capacity != 0); + + OCILobLocator* locator (reinterpret_cast (b.size)); + + ub8 size (0); + sword r (OCILobGetLength2(conn_.handle (), + err, + locator, + &size)); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + ub1 piece (OCI_FIRST_PIECE); + ub8 read (size); + + do + { + r = OCILobRead2 (conn_.handle (), + err, + locator, + &read, + 0, + 1, + b.buffer, + b.capacity, + piece, + 0, + 0, + 0, + SQLCS_IMPLICIT); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + chunk_position cp; + + if (piece == OCI_FIRST_PIECE) + cp = read == size ? one_chunk : first_chunk; + else if (r == OCI_NEED_DATA) + cp = next_chunk; + else + cp = last_chunk; + + if (!(*b.callback.result) (b.callback_context, + b.buffer, + static_cast (read), + cp)) + throw database_exception (24343, "user defined callback error"); + + piece = OCI_NEXT_PIECE; + + } while (r == OCI_NEED_DATA); + } + } + } + statement:: ~statement () { @@ -257,13 +326,12 @@ namespace odb const string& s, binding& cond, binding& data, - std::size_t lob_prefetch_len) + std::size_t lob_prefetch_size) : statement (conn, s), - data_ (data), done_ (false) { bind_param (cond.bind, cond.count, 0); - bind_result (data.bind, data.count, lob_prefetch_len); + bind_result (data.bind, data.count, lob_prefetch_size); } void select_statement:: @@ -272,10 +340,10 @@ namespace odb if (!done_) free_result (); - // @@ Retrieve a single row into the already bound output buffers - // as an optimization? This will avoid multiple server round-trips - // in the case of a single object load. Keep in mind the implications - // on LOB prefetch. + // @@ Retrieve a single row into the already bound output buffers as an + // optimization? This will avoid multiple server round-trips in the case + // of a single object load. Keep the implications on LOB prefetch in + // mind. // sword r (OCIStmtExecute (conn_.handle (), stmt_, @@ -330,85 +398,6 @@ namespace odb } } - void select_statement:: - stream_result_lobs () - { - OCIError* err (conn_.error_handle()); - - for (size_t i (0); i < data_.count; ++i) - { - // Only stream if the bind specifies a LOB type, and the LOB - // value is not NULL, and a result callback has been provided. - // - if ((data_.bind[i].type == SQLT_BLOB || - data_.bind[i].type == SQLT_CLOB) && - *data_.bind[i].indicator != -1 && - data_.bind[i].callback.result != 0) - { - // @@ If data_.bind[i].capacity is 0, we will be stuck in an - // infinite loop. - // - assert (data_.bind[i].capacity != 0); - - OCILobLocator* locator ( - reinterpret_cast (data_.bind[i].size)); - - ub8 length (0); - sword r (OCILobGetLength2(conn_.handle (), - err, - locator, - &length)); - - if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) - translate_error (err, r); - - for (ub8 total (0), read (0); total < length; total += read) - { - read = length - total; - read = read > data_.bind[i].capacity ? - data_.bind[i].capacity : read; - - // The call to OCILobRead2 does not need to know when the last - // piece is being requested. - // - ub1 oci_piece (total == 0 ? OCI_FIRST_PIECE : OCI_NEXT_PIECE); - - r = OCILobRead2 (conn_.handle (), - err, - locator, - &read, - 0, - 1, // Starting offset. The first position is 1. - // This parameter is only used by OCI on the - // first call when polling. - data_.bind[i].buffer, - data_.bind[i].capacity, - oci_piece, - 0, - 0, - 0, - SQLCS_IMPLICIT); - - if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) - translate_error (err, r); - - bind::piece user_piece; - if (oci_piece == OCI_FIRST_PIECE) - user_piece = read == length ? bind::whole : bind::first; - else - user_piece = total + read < length ? bind::next : bind::last; - - if (!(*data_.bind[i].callback.result) ( - data_.bind[i].callback_context, - data_.bind[i].buffer, - read, - user_piece)) - break; - } - } - } - } - // // insert_statement // @@ -419,7 +408,7 @@ namespace odb ub4, // iter ub4, // index void** buffer, - ub4* len, + ub4* size, ub1* piece, void** indicator) { @@ -428,7 +417,7 @@ namespace odb bind& b (*static_cast (context)); *buffer = 0; - *len = 0; + *size = 0; *piece = OCI_ONE_PIECE; b.indicator = -1; *indicator = &b.indicator; @@ -442,7 +431,7 @@ namespace odb ub4, // iter ub4, // index void** buffer, - ub4** len, + ub4** size, ub1*, // piece void** indicator, ub2** rcode) @@ -454,10 +443,10 @@ namespace odb #if (OCI_MAJOR_VERSION == 11 && OCI_MINOR_VERSION >=2) \ || OCI_MAJOR_VERSION > 11 *buffer = &b.id.value64; - **len = sizeof (unsigned long long); + **size = sizeof (unsigned long long); #else *buffer = &b.id.value32; - **len = sizeof (unsigned int); + **size = sizeof (unsigned int); #endif *indicator = &b.indicator; diff --git a/odb/oracle/statement.hxx b/odb/oracle/statement.hxx index b4e6601..add263a 100644 --- a/odb/oracle/statement.hxx +++ b/odb/oracle/statement.hxx @@ -48,13 +48,21 @@ namespace odb // lost OCIDefine resources. // void - bind_result (bind*, - std::size_t count, - std::size_t lob_prefetch_len = 0); + bind_result (bind*, + std::size_t count, + std::size_t lob_prefetch_size = 0); + + // Stream the result LOBs, calling user callbacks where necessary. + // + void + stream_result_lobs (); protected: connection& conn_; auto_handle stmt_; + + bind* result_binds_; + std::size_t count_; }; class LIBODB_ORACLE_EXPORT select_statement: public statement @@ -67,7 +75,7 @@ namespace odb const std::string& statement, binding& cond, binding& data, - std::size_t lob_prefetch_len = 0); + std::size_t lob_prefetch_size = 0); enum result { success, @@ -83,17 +91,11 @@ namespace odb void free_result (); - // Stream the result lobs, invoking user callbacks where necessary. - // - void - stream_result_lobs (); - private: select_statement (const select_statement&); select_statement& operator= (const select_statement&); private: - binding& data_; bool done_; }; -- cgit v1.1