diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2024-02-01 15:47:37 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2024-02-01 15:47:37 +0300 |
commit | 62e234c114d2b6ead93a1d39593c66b648c3d0a6 (patch) | |
tree | ce6740b4508eb29400490b20efc8e100e38a7b7f /libodb-oracle/odb/oracle/statement.cxx | |
parent | 9072842a023c4b65ecb141292c4b63417fee1b98 (diff) |
Turn libodb-oracle repository into package for muti-package repositorylibodb-oracle
Diffstat (limited to 'libodb-oracle/odb/oracle/statement.cxx')
-rw-r--r-- | libodb-oracle/odb/oracle/statement.cxx | 2055 |
1 files changed, 2055 insertions, 0 deletions
diff --git a/libodb-oracle/odb/oracle/statement.cxx b/libodb-oracle/odb/oracle/statement.cxx new file mode 100644 index 0000000..93d8a4a --- /dev/null +++ b/libodb-oracle/odb/oracle/statement.cxx @@ -0,0 +1,2055 @@ +// file : odb/oracle/statement.cxx +// license : ODB NCUEL; see accompanying LICENSE file + +#include <oci.h> + +#include <cstring> // std::strlen, std::memset +#include <cassert> + +#include <odb/tracer.hxx> +#include <odb/exceptions.hxx> // object_not_persistent +#include <odb/details/unused.hxx> + +#include <odb/oracle/database.hxx> +#include <odb/oracle/statement.hxx> +#include <odb/oracle/connection.hxx> +#include <odb/oracle/auto-descriptor.hxx> +#include <odb/oracle/error.hxx> +#include <odb/oracle/exceptions.hxx> + +#include <odb/oracle/details/number.hxx> + +using namespace std; + +namespace odb +{ + namespace oracle + { + // Mapping of bind::buffer_type values for parameter buffers to their + // equivalent external OCI typecode identifiers. + // + static const ub4 param_sqlt_lookup[bind::last] = + { + SQLT_INT, // bind::integer + SQLT_UIN, // bind::uinteger + SQLT_BFLOAT, // bind::binary_float + SQLT_BDOUBLE, // bind::binary_double + SQLT_NUM, // bind::number + SQLT_DAT, // bind::date + SQLT_TIMESTAMP, // bind::timestamp + SQLT_INTERVAL_YM, // bind::interval_ym + SQLT_INTERVAL_DS, // bind::interval_ds + SQLT_CHR, // bind::string + SQLT_CHR, // bind::nstring + SQLT_BIN, // bind::raw + SQLT_LBI, // bind::blob + SQLT_LNG, // bind::clob + SQLT_LNG // bind::nclob + }; + + // Mapping of bind::buffer_type values for result buffers to their + // equivalent external OCI typecode identifiers. + // + static const ub4 result_sqlt_lookup[bind::last] = + { + SQLT_INT, // bind::integer + SQLT_UIN, // bind::uinteger + SQLT_BFLOAT, // bind::binary_float + SQLT_BDOUBLE, // bind::binary_double + SQLT_NUM, // bind::number + SQLT_DAT, // bind::date + SQLT_TIMESTAMP, // bind::timestamp + SQLT_INTERVAL_YM, // bind::interval_ym + SQLT_INTERVAL_DS, // bind::interval_ds + SQLT_CHR, // bind::string + SQLT_CHR, // bind::nstring + SQLT_BIN, // bind::raw + SQLT_BLOB, // bind::blob + SQLT_CLOB, // bind::clob + SQLT_CLOB // bind::nclob + }; + + template <typename T> + static 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 it, // iteration + ub4, // index + void** buffer, + ub4* size, + ub1* piece, + void** indicator) + { + bind& b (*static_cast<bind*> (context)); + + // 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 (*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 (!(*cb->callback.param) ( + cb->context.param, + &l->position, + const_cast<const void**> (buffer), + size, + &pos, + l->buffer->data (), + static_cast<ub4> (l->buffer->capacity ()))) + return OCI_ERROR; + + switch (pos) + { + case chunk_one: + { + *piece = OCI_ONE_PIECE; + break; + } + case chunk_first: + { + *piece = OCI_FIRST_PIECE; + break; + } + case chunk_next: + { + *piece = OCI_NEXT_PIECE; + break; + } + case chunk_last: + { + *piece = OCI_LAST_PIECE; + break; + } + } + } + else + *piece = OCI_ONE_PIECE; + + *indicator = ind; + + return OCI_CONTINUE; + } + + // + // statement + // + + statement:: + ~statement () + { + if (stmt_ == 0) + return; + + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->deallocate (conn_, *this); + } + + // Unbind (free) parameter descriptors. + // + for (size_t i (0); i < usize_; ++i) + { + ub4 t; + bind* b (udata_[i].bind); + + switch (udata_[i].type) + { + case bind::timestamp: + { + if (b != 0) + static_cast<datetime*> (b->buffer)->descriptor = 0; + + t = OCI_DTYPE_TIMESTAMP; + break; + } + case bind::interval_ym: + { + if (b != 0) + static_cast<interval_ym*> (b->buffer)->descriptor = 0; + + t = OCI_DTYPE_INTERVAL_YM; + break; + } + case bind::interval_ds: + { + if (b != 0) + static_cast<interval_ds*> (b->buffer)->descriptor = 0; + + t = OCI_DTYPE_INTERVAL_DS; + break; + } + default: + { + assert (false); + return; + } + } + + OCIDescriptorFree (udata_[i].value, t); + } + + delete[] udata_; + } + + statement:: + statement (connection_type& conn, + const string& text, + statement_kind sk, + const binding* process, + bool optimize) + : conn_ (conn), udata_ (0), usize_ (0) + { + init (text.c_str (), text.size (), sk, process, optimize); + } + + statement:: + statement (connection_type& conn, + const char* text, + statement_kind sk, + const binding* process, + bool optimize) + : conn_ (conn), udata_ (0), usize_ (0) + { + init (text, strlen (text), sk, process, optimize); + } + + void statement:: + init (const char* text, + size_t text_size, + statement_kind sk, + const binding* proc, + bool optimize) + { + string tmp; + if (proc != 0) + { + switch (sk) + { + case statement_select: + process_select (tmp, + text, + &proc->bind->buffer, proc->count, sizeof (bind), + '"', '"', + optimize, + false); // No AS in JOINs. + break; + case statement_insert: + process_insert (tmp, + text, + &proc->bind->buffer, proc->count, sizeof (bind), + ':'); + break; + case statement_update: + process_update (tmp, + text, + &proc->bind->buffer, proc->count, sizeof (bind), + ':'); + break; + case statement_delete: + case statement_generic: + assert (false); + } + + text = tmp.c_str (); + text_size = tmp.size (); + } + + // Empty statement. + // + if (*text == '\0') + return; + + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + { + // Temporarily store the statement text in unbind data so that + // text() which may be called by the tracer can access it. + // + udata_ = reinterpret_cast<unbind*> (const_cast<char*> (text)); + t->prepare (conn_, *this); + udata_ = 0; + } + } + + OCIError* err (conn_.error_handle ()); + OCIStmt* handle (0); + + sword r (OCIStmtPrepare2 (conn_.handle (), + &handle, + err, + reinterpret_cast<const OraText*> (text), + static_cast<ub4> (text_size), + 0, + 0, + OCI_NTV_SYNTAX, + OCI_DEFAULT)); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (conn_, r); + + stmt_.reset (handle, OCI_STRLS_CACHE_DELETE, err); + } + + const char* statement:: + text () const + { + if (stmt_ == 0) + // See init() above for details on what's going on here. + // + return udata_ != 0 ? reinterpret_cast<const char*> (udata_) : ""; + + OCIError* err (conn_.error_handle ()); + + OraText* s (0); + sword r (OCIAttrGet (stmt_, + OCI_HTYPE_STMT, + &s, + 0, + OCI_ATTR_STATEMENT, + err)); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + return reinterpret_cast<char*> (s); + } + + ub4 statement:: + 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. + // + { + size_t un (0); + + for (size_t i (0); i < n; ++i) + { + if (b[i].buffer == 0) // Skip NULL entries. + continue; + + switch (b[i].type) + { + case bind::timestamp: + { + datetime* dt (static_cast<datetime*> (b[i].buffer)); + if (dt->descriptor == 0 && (dt->flags & descriptor_free) == 0) + un++; + break; + } + case bind::interval_ym: + { + interval_ym* iym (static_cast<interval_ym*> (b[i].buffer)); + if (iym->descriptor == 0 && (iym->flags & descriptor_free) == 0) + un++; + break; + } + case bind::interval_ds: + { + interval_ds* ids (static_cast<interval_ds*> (b[i].buffer)); + if (ids->descriptor == 0 && (ids->flags & descriptor_free) == 0) + un++; + break; + } + default: + break; + } + } + + // 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]; + } + + bool seen_lob (false); + sword r; + OCIError* err (conn_.error_handle ()); + OCIEnv* env (conn_.database ().environment ()); + + ub4 i (0); + for (bind* end (b + n); b != end; ++b) + { + if (b->buffer == 0) // Skip NULL entries. + continue; + + i++; // Column index is 1-based. + + void* value (0); + sb4 capacity; + ub2* size (0); + bool callback (b->callback != 0); + + switch (b->type) + { + case bind::timestamp: + { + for (size_t i (0); i != batch; ++i) + { + datetime* dt ( + static_cast<datetime*> (offset (b->buffer, i, skip))); + + void* pd (0); // Pointer to descriptor. + + if (dt->descriptor == 0) + { + 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 + pd = &dt->descriptor; + + if (i == 0) + value = pd; + } + + capacity = static_cast<sb4> (sizeof (OCIDateTime*)); + break; + } + case bind::interval_ym: + { + for (size_t i (0); i != batch; ++i) + { + interval_ym* iym ( + static_cast<interval_ym*> (offset (b->buffer, i, skip))); + + void* pd (0); // Pointer to descriptor. + + if (iym->descriptor == 0) + { + 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 + pd = &iym->descriptor; + + if (i == 0) + value = pd; + } + + capacity = static_cast<sb4> (sizeof (OCIInterval*)); + break; + } + case bind::interval_ds: + { + for (size_t i (0); i != batch; ++i) + { + interval_ds* ids ( + static_cast<interval_ds*> (offset (b->buffer, i, skip))); + + void* pd (0); // Pointer to descriptor. + + if (ids->descriptor == 0) + { + 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 + pd = &ids->descriptor; + + if (i == 0) + value = pd; + } + + capacity = static_cast<sb4> (sizeof (OCIInterval*)); + break; + } + case bind::blob: + case bind::clob: + case bind::nclob: + { + seen_lob = true; + + lob* l (static_cast<lob*> (b->buffer)); + + if (l->buffer == 0) + { + details::buffer& lob_buffer (conn_.lob_buffer ()); + + if (lob_buffer.capacity () == 0) + lob_buffer.capacity (4096); + + // Generally, we should not modify the image since that would + // break the thread-safety guarantee of the query expression. + // However, in Oracle, LOBs cannot be used in queries so we can + // make an exception here. + // + for (size_t i (0); i != batch;) + { + l->buffer = &lob_buffer; + l = static_cast<lob*> (offset (b->buffer, ++i, skip)); + } + } + + assert (callback); + value = 0; + + // When binding LOB parameters, the capacity must be greater than + // 4000 and less than the maximum LOB length in bytes. If it is + // not, OCI returns an error. Other than this, the capacity seems + // to be irrelevant to OCI bind behaviour for LOB parameters when + // used with callbacks. + // + 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: + { +#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 + value = callback ? 0 : b->buffer; + capacity = static_cast<sb4> (b->capacity); + size = b->size; + + break; + } + } + + OCIBind* h (0); + r = OCIBindByPos (stmt_, + &h, + err, + i, + value, + capacity, + param_sqlt_lookup[b->type], + b->indicator, + size, + 0, + 0, + 0, + callback ? OCI_DATA_AT_EXEC : OCI_DEFAULT); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + // Set the character set form for national strings. + // + if (b->type == bind::nstring || b->type == bind::nclob) + { + ub1 form (SQLCS_NCHAR); + r = OCIAttrSet (h, + OCI_HTYPE_BIND, + &form, + 0, + OCI_ATTR_CHARSET_FORM, + err); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + + if (seen_lob && (b->type == bind::string || b->type == bind::nstring)) + { + // Set the maximum data size for all string types. If this is not set + // Oracle server will implicitly calculate this maximum size. If the + // calculated size exceeds 4000 bytes (which may occur if a character + // set conversion is required) and the string is bound after a LOB + // binding, the server will return an ORA-24816 error. + // + sb4 n (4000); + r = OCIAttrSet (h, + OCI_HTYPE_BIND, + &n, + 0, + OCI_ATTR_MAXDATA_SIZE, + err); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + + if (callback) + { + r = OCIBindDynamic ( + h, err, b, &odb_oracle_param_callback_proxy, 0, 0); + + 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; + } + + ub4 statement:: + bind_result (bind* b, size_t c, size_t p) + { + ODB_POTENTIALLY_UNUSED (p); + + sword r; + OCIError* err (conn_.error_handle ()); + OCIEnv* env (conn_.database ().environment ()); + + ub4 i (0); + for (bind* end (b + c); b != end; ++b) + { + if (b->buffer == 0) // Skip NULL entries. + continue; + + i++; // Column index is 1-based. + + void* value; + sb4 capacity; + ub2* size (0); + + switch (b->type) + { + case bind::timestamp: + { + datetime* dt (static_cast<datetime*> (b->buffer)); + + if (dt->descriptor == 0) + { + assert ((dt->flags & descriptor_cache) && + (dt->flags & descriptor_free)); + + void* d (0); + r = OCIDescriptorAlloc (env, &d, OCI_DTYPE_TIMESTAMP, 0, 0); + + if (r != OCI_SUCCESS) + throw invalid_oci_handle (); + + dt->descriptor = static_cast<OCIDateTime*> (d); + dt->environment = env; + dt->error = err; + } + + 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) + { + assert ((iym->flags & descriptor_cache) && + (iym->flags & descriptor_free)); + + void* d (0); + r = OCIDescriptorAlloc (env, &d, OCI_DTYPE_INTERVAL_YM, 0, 0); + + if (r != OCI_SUCCESS) + throw invalid_oci_handle (); + + iym->descriptor = static_cast<OCIInterval*> (d); + iym->environment = env; + iym->error = err; + } + + 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) + { + assert ((ids->flags & descriptor_cache) && + (ids->flags & descriptor_free)); + + void* d (0); + r = OCIDescriptorAlloc (env, &d, OCI_DTYPE_INTERVAL_DS, 0, 0); + + if (r != OCI_SUCCESS) + throw invalid_oci_handle (); + + ids->descriptor = static_cast<OCIInterval*> (d); + ids->environment = env; + ids->error = err; + } + + value = &ids->descriptor; + capacity = static_cast<sb4> (sizeof (OCIInterval*)); + + break; + } + case bind::blob: + case bind::clob: + case bind::nclob: + { + lob* l (static_cast<lob*> (b->buffer)); + + if (l->locator == 0) + { + void* d (0); + r = OCIDescriptorAlloc (env, &d, OCI_DTYPE_LOB, 0, 0); + + if (r != OCI_SUCCESS) + throw invalid_oci_handle (); + + l->locator = static_cast<OCILobLocator*> (d); + l->environment = env; + l->error = err; + } + + value = &l->locator; + capacity = static_cast<sb4> (sizeof (OCILobLocator*)); + + break; + } + default: + { +#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 + value = b->buffer; + capacity = static_cast<sb4> (b->capacity); + size = b->size; + + break; + } + } + + OCIDefine* h (0); + r = OCIDefineByPos (stmt_, + &h, + err, + i, + value, + capacity, + result_sqlt_lookup[b->type], + b->indicator, + size, + 0, + OCI_DEFAULT); + + 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. + // +#if (OCI_MAJOR_VERSION == 11 && OCI_MINOR_VERSION >= 1) \ + || OCI_MAJOR_VERSION > 11 + if (b->type == bind::blob || + b->type == bind::clob || + b->type == bind::nclob) + { + + if (p != 0) + { + ub4 n (static_cast<ub4> (p)); + + r = OCIAttrSet (h, + OCI_HTYPE_DEFINE, + &n, + 0, + OCI_ATTR_LOBPREFETCH_SIZE, + err); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + } + else +#endif + if (b->type == bind::nstring) + { + ub1 form (SQLCS_NCHAR); + + r = OCIAttrSet (h, + OCI_HTYPE_DEFINE, + &form, + 0, + OCI_ATTR_CHARSET_FORM, + err); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + } + + return i; + } + + void statement:: + stream_result (bind* b, size_t c, void* obase, void* nbase) + { + OCIError* err (conn_.error_handle ()); + + for (bind* end (b + c); b != end; ++b) + { + if (b->buffer == 0) // Skip NULL entries. + continue; + + // Only stream if the bind specifies a LOB type. + // + if (b->type == bind::blob || + b->type == bind::clob || + b->type == bind::nclob) + { + lob* l; + sb2* ind; + lob_callback* cb; + + if (obase == 0) + { + l = static_cast<lob*> (b->buffer); + ind = b->indicator; + cb = b->callback; + } + else + { + // Re-base the pointers. + // + char* ob (static_cast<char*> (obase)); + char* nb (static_cast<char*> (nbase)); + + char* p (static_cast<char*> (b->buffer)); + assert (ob <= p); + l = reinterpret_cast<lob*> (nb + (p - ob)); + + if (b->indicator == 0) + ind = 0; + else + { + p = reinterpret_cast<char*> (b->indicator); + assert (ob <= p); + ind = reinterpret_cast<sb2*> (nb + (p - ob)); + } + + p = reinterpret_cast<char*> (b->callback); + assert (ob <= p); + cb = reinterpret_cast<lob_callback*> (nb + (p - ob)); + } + + // Nothing to do if the LOB value is NULL or the result callback + // hasn't been provided. + // + if ((ind != 0 && *ind == -1) || cb->callback.result == 0) + continue; + + ub4 position (0); // Position context. + ub1 piece (OCI_FIRST_PIECE); + + // Setting the value pointed to by the byte_amt argument to 0 on the + // first call to OCILobRead2 instructs OCI to remain in a polling + // state until the EOF is reached, at which point OCILobRead2 will + // return OCI_SUCCESS. + // + ub8 read (0); + ub1 cs_form (b->type == bind::nclob ? SQLCS_NCHAR : SQLCS_IMPLICIT); + + // Allocate buffer space if necessary. + // + details::buffer& lob_buffer (conn_.lob_buffer ()); + + if (lob_buffer.capacity () == 0) + lob_buffer.capacity (4096); + + sword r; + do + { + r = OCILobRead2 (conn_.handle (), + err, + l->locator, + &read, + 0, + 1, + lob_buffer.data (), + lob_buffer.capacity (), + piece, + 0, + 0, + 0, + cs_form); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (conn_, r); + + chunk_position cp; + + if (piece == OCI_FIRST_PIECE) + cp = r == OCI_SUCCESS ? chunk_one : chunk_first; + else if (r == OCI_NEED_DATA) + cp = chunk_next; + else + cp = chunk_last; + + piece = OCI_NEXT_PIECE; + + // OCI generates and ORA-24343 error when an error code is + // returned from a user callback. We simulate this. + // + if (!(*cb->callback.result) ( + cb->context.result, + &position, + lob_buffer.data (), + static_cast<ub4> (read), + cp)) + throw database_exception (24343, "user defined callback error"); + + } while (r == OCI_NEED_DATA); + } + } + } + + // + // 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 + // + + generic_statement:: + generic_statement (connection_type& conn, const string& text) + : statement (conn, + text, statement_generic, + 0, false), + bound_ (false) + { + init (); + } + + generic_statement:: + generic_statement (connection_type& conn, const char* text) + : statement (conn, + text, statement_generic, + 0, false), + bound_ (false) + { + init (); + } + + void generic_statement:: + init () + { + OCIError* err (conn_.error_handle ()); + + sword r (OCIAttrGet (stmt_, + OCI_HTYPE_STMT, + &stmt_type_, + 0, + OCI_ATTR_STMT_TYPE, + err)); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + + generic_statement:: + ~generic_statement () + { + } + + unsigned long long generic_statement:: + execute () + { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + + sword r (0); + + OCISvcCtx* handle (conn_.handle ()); + OCIError* err (conn_.error_handle ()); + + if (stmt_type_ == OCI_STMT_SELECT) + { + // Do not prefetch any rows. + // + r = OCIStmtExecute (handle, stmt_, err, 0, 0, 0, 0, OCI_DEFAULT); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (conn_, r); + + // In order to successfully execute a select statement, OCI/Oracle + // requires that there be OCIDefine handles provided for all select + // list columns. Since we are not interested in any data returned by + // the select statement, all buffer pointers, indicator variable + // pointers, and data length pointers are specified as NULL (we still + // specify a valid data type identifier; not doing so results in + // undefined behavior). This results in truncation errors being + // returned for all attempted row fetches. However, cursor behaves + // normally allowing us to return the row count for a select + // statement. Note also that we only need to do this once. + // + if (!bound_) + { + for (ub4 i (1); ; ++i) + { + auto_descriptor<OCIParam> param; + { + OCIParam* p (0); + r = OCIParamGet (stmt_, + OCI_HTYPE_STMT, + err, + reinterpret_cast<void**> (&p), + i); + + if (r == OCI_ERROR) // No more result columns. + break; + + param.reset (p); + } + + ub2 data_type; + r = OCIAttrGet (param, + OCI_DTYPE_PARAM, + &data_type, + 0, + OCI_ATTR_DATA_TYPE, + err); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + // No need to keep track of the OCIDefine handles - these will + // be deallocated with the statement. + // + OCIDefine* define (0); + r = OCIDefineByPos (stmt_, + &define, + err, + i, + 0, // NULL value buffer pointer + 0, // zero length value buffer + data_type, + 0, // NULL indicator pointer + 0, // NULL length data pointer + 0, // NULL column level return code pointer + OCI_DEFAULT); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + + bound_ = true; + } + + for (;;) + { + r = OCIStmtFetch2 (stmt_, err, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT); + + if (r == OCI_NO_DATA) + break; + else if (r == OCI_ERROR) + { + sb4 e; + r = OCIErrorGet (err, 1, 0, &e, 0, 0, OCI_HTYPE_ERROR); + + // ORA-01406 is returned if there is a truncation error. We expect + // and ignore this error. + // + if (e != 1406) + translate_error (conn_, r); + } + else if (r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + } + else + { + // OCIStmtExecute requires a non-zero iters param for DML statements. + // + r = OCIStmtExecute (handle, stmt_, err, 1, 0, 0, 0, OCI_DEFAULT); + + 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); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + return row_count; + } + + // + // select_statement + // + + select_statement:: + ~select_statement () + { + } + + select_statement:: + select_statement (connection_type& conn, + const string& text, + bool process, + bool optimize, + binding& param, + binding& result, + size_t lob_prefetch_size) + : statement (conn, + text, statement_select, + (process ? &result : 0), optimize), + result_ (result), + done_ (true) + { + if (!empty ()) + { + bind_param (param.bind, param.count); + result_count_ = bind_result ( + result.bind, result.count, lob_prefetch_size); + } + } + + select_statement:: + select_statement (connection_type& conn, + const char* text, + bool process, + bool optimize, + binding& param, + binding& result, + size_t lob_prefetch_size) + : statement (conn, + text, statement_select, + (process ? &result : 0), optimize), + result_ (result), + done_ (true) + { + if (!empty ()) + { + bind_param (param.bind, param.count); + result_count_ = bind_result ( + result.bind, result.count, lob_prefetch_size); + } + } + + select_statement:: + select_statement (connection_type& conn, + const string& text, + bool process, + bool optimize, + binding& result, + size_t lob_prefetch_size) + : statement (conn, + text, statement_select, + (process ? &result : 0), optimize), + result_ (result), + done_ (true) + { + if (!empty ()) + { + result_count_ = bind_result ( + result.bind, result.count, lob_prefetch_size); + } + } + + select_statement:: + select_statement (connection_type& conn, + const char* text, + bool process, + bool optimize, + binding& result, + size_t lob_prefetch_size) + : statement (conn, + text, statement_select, + (process ? &result : 0), optimize), + result_ (result), + done_ (true) + { + if (!empty ()) + { + result_count_ = bind_result ( + result.bind, result.count, lob_prefetch_size); + } + } + + void select_statement:: + execute () + { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + + OCIError* err (conn_.error_handle ()); + + // @@ 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. + // + sword r (OCIStmtExecute (conn_.handle (), + stmt_, + err, + 0, + 0, + 0, + 0, + OCI_DEFAULT)); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (conn_, r); + + done_ = r == OCI_NO_DATA; + +#ifndef NDEBUG + ub4 n (0); + r = OCIAttrGet (stmt_, OCI_HTYPE_STMT, &n, 0, OCI_ATTR_PARAM_COUNT, err); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + // Make sure that the number of columns in the result returned by + // the database matches the number that we expect. A common cause + // of this assertion is a native view with a number of data members + // not matching the number of columns in the SELECT-list. + // + assert (n == result_count_); +#endif + } + + select_statement::result select_statement:: + fetch () + { + if (!done_) + { + change_callback* cc (result_.change_callback); + + if (cc != 0 && cc->callback != 0) + (cc->callback) (cc->context); + + sword r (OCIStmtFetch2 (stmt_, + conn_.error_handle (), + 1, + OCI_FETCH_NEXT, + 0, + OCI_DEFAULT)); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (conn_, r); + else if (r == OCI_NO_DATA) + done_ = true; + } + + return done_ ? no_data : success; + } + + void select_statement:: + free_result () + { + if (!done_) + { + sword r (OCIStmtFetch2 (stmt_, + conn_.error_handle (), + 0, + OCI_FETCH_NEXT, + 0, + OCI_DEFAULT)); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (conn_, r); + + done_ = true; + } + } + + // + // insert_statement + // + + extern "C" sb4 + odb_oracle_returning_in (void* context, + OCIBind*, // bind + ub4 it, // iter + ub4, // index + void** buffer, + ub4* size, + ub1* piece, + void** indicator) + { + 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; + + sb2* ind (offset (ret.bind[0].indicator, it, ret.skip)); + *ind = -1; + *indicator = ind; + + return OCI_CONTINUE; + } + + extern "C" sb4 + odb_oracle_returning_out (void* context, + OCIBind*, // bind + ub4 it, // iter + ub4, // index + void** buffer, + ub4** size, + ub1* piece, + void** indicator, + ub2** rcode) + { + 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); + + // Offset the data based on the current iteration and skip size. + // + *buffer = offset (b.buffer, it, skip); + + 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_; + } + + // 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; + } + + insert_statement:: + ~insert_statement () + { + } + + insert_statement:: + insert_statement (connection_type& conn, + const string& text, + bool process, + binding& param, + binding* returning) + : bulk_statement (conn, + text, statement_insert, + (process ? ¶m : 0), false, + param.batch, param.status), + ret_ (returning) + { + init (param); + } + + insert_statement:: + insert_statement (connection_type& conn, + const char* text, + bool process, + binding& param, + binding* returning) + : bulk_statement (conn, + text, statement_insert, + (process ? ¶m : 0), false, + param.batch, param.status), + ret_ (returning) + { + init (param); + } + + void insert_statement:: + init (binding& param) + { + 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, + b->capacity, + param_sqlt_lookup[b->type], + 0, + 0, + 0, + 0, + 0, + OCI_DATA_AT_EXEC)); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + + r = OCIBindDynamic (h, + err, + this, + &odb_oracle_returning_in, + this, + &odb_oracle_returning_out); + + if (r == OCI_ERROR || r == OCI_INVALID_HANDLE) + translate_error (err, r); + } + } + + size_t insert_statement:: + execute (size_t n, multiple_exceptions* mex) + { + OCIError* err (conn_.error_handle ()); + + 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); + + if (result_) // If fetch() hasn't translated the error. + translate_error (err, r, &conn_, 0, mex_); // Can return. + + return n_; + } + + // Store the last returned id size (see odb_oracle_returning_out() + // for details). + // + if (ret_ != 0 && ret_prev_ != 0) + *ret_prev_ = static_cast<ub2> (ret_size_); + + if (status_ == 0) // Non-batch mode. + fetch (OCI_SUCCESS, 0); + else + { + fetch (status_[i_] == 0 ? OCI_SUCCESS : OCI_ERROR, status_[i_]); + } + + return n_; + } + + // + // update_statement + // + + update_statement:: + ~update_statement () + { + } + + update_statement:: + update_statement (connection_type& conn, + const string& text, + bool process, + binding& param) + : 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, param.batch, param.skip); + } + + update_statement:: + update_statement (connection_type& conn, + const string& 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); + } + + 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) + { + assert (param.batch == 1); // Specify unique_hint explicitly. + + if (!empty ()) + bind_param (param.bind, param.count, param.batch, param.skip); + } + + 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); + } + + 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, &conn_, 0, mex_); // Can return. + return n_; + } + + // Figure out the affected (matched, not necessarily updated) + // row count. + // + result_ = affected (unique_); + + return n_; + } + + // + // delete_statement + // + + delete_statement:: + ~delete_statement () + { + } + + delete_statement:: + delete_statement (connection_type& conn, + const string& text, + binding& param) + : bulk_statement (conn, + text, statement_delete, + 0, false, + param.batch, param.status), + unique_ (false) + { + 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) + : bulk_statement (conn, + text, statement_delete, + 0, false, + param.batch, param.status), + unique_ (false) + { + 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 char* 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); + } + + size_t delete_statement:: + execute (size_t n, multiple_exceptions* mex) + { + sword r (bulk_statement::execute (n, mex)); + OCIError* err (conn_.error_handle ()); + + // 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, &conn_, 0, mex_); // Can return. + return n_; + } + + // Figure out the affected row count. + // + result_ = affected (unique_); + + return n_; + } + } +} |