Skip to content

Commit

Permalink
Show row values in errors in Oracle backend
Browse files Browse the repository at this point in the history
Use OCI_BATCH_ERRORS mode to retrieve information about the errors
during bulk operations and use it to show the parameter values for the
first failing row.

Also add a generally useful RAII helper wrapping OCIHandle{Alloc,Free}
and use it for temporary OCIError allocation.
  • Loading branch information
vadz committed Jan 30, 2025
1 parent d135562 commit a4188c8
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 4 deletions.
4 changes: 4 additions & 0 deletions include/soci/oracle/soci-oracle.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ struct SOCI_ORACLE_DECL oracle_statement_backend : details::statement_backend
long long get_affected_rows() override;
int get_number_of_rows() override;
std::string get_parameter_name(int index) const override;
int get_row_to_dump() const override { return error_row_; }

std::string rewrite_for_procedure_call(std::string const &query) override;

Expand Down Expand Up @@ -253,6 +254,9 @@ struct SOCI_ORACLE_DECL oracle_statement_backend : details::statement_backend
// Wrapper for OCIAttrGet(), throws on error.
template <typename T>
T get_statement_attr(int attr) const;

// First row with the error for bulk operations or -1.
int error_row_ = -1;
};

struct SOCI_ORACLE_DECL oracle_rowid_backend : details::rowid_backend
Expand Down
79 changes: 79 additions & 0 deletions src/backends/oracle/handle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// Copyright (C) 2025 Vadim Zeitlin
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef SOCI_ORACLE_HANDLE_H_INCLUDED
#define SOCI_ORACLE_HANDLE_H_INCLUDED

#include "soci/oracle/soci-oracle.h"

namespace soci
{

namespace details
{

namespace oracle
{

template <typename Handle>
struct oci_traits;

template <>
struct oci_traits<OCIStmt>
{
static constexpr ub4 type = OCI_HTYPE_STMT;
};

template <>
struct oci_traits<OCIError>
{
static constexpr ub4 type = OCI_HTYPE_ERROR;
};

template <typename OCIHandle>
class handle
{
public:
static constexpr ub4 HandleType = oci_traits<OCIHandle>::type;

explicit handle(OCIEnv *envhp)
: handle_(NULL)
{
sword res = OCIHandleAlloc(envhp,
reinterpret_cast<dvoid**>(&handle_),
HandleType, 0, 0);
if (res != OCI_SUCCESS)
{
throw oracle_soci_error("Failed to allocate handler", 0);
}
}

~handle()
{
OCIHandleFree(handle_, HandleType);
}

handle(handle const &) = delete;
handle& operator=(handle const &) = delete;

OCIHandle* get() { return handle_; }
operator OCIHandle*() { return handle_; }

// This is needed to allow passing handle to OCI functions.
void** ptr() { return reinterpret_cast<void**>(&handle_); }

private:
OCIHandle* handle_;
};

} // namespace oracle

} // namespace details

} // namespace soci

#endif // SOCI_ORACLE_HANDLE_H_INCLUDED
63 changes: 59 additions & 4 deletions src/backends/oracle/statement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#define SOCI_ORACLE_SOURCE
#include "soci/oracle/soci-oracle.h"
#include "error.h"
#include "handle.h"
#include "soci/soci-backend.h"
#include <cctype>
#include <cstdio>
Expand All @@ -27,12 +28,12 @@ using namespace soci::details::oracle;
namespace
{

template <typename T>
T get_oci_attr(OCIStmt* hp, int attr, OCIError* errhp)
template <typename T, typename OCIHandle>
T get_oci_attr(OCIHandle* hp, int attr, OCIError* errhp)
{
T value;
sword res = OCIAttrGet(hp,
OCI_HTYPE_STMT,
oci_traits<OCIHandle>::type,
&value,
0,
attr,
Expand All @@ -47,6 +48,12 @@ T get_oci_attr(OCIStmt* hp, int attr, OCIError* errhp)
return value;
}

template <typename T, typename OCIHandle>
T get_oci_attr(handle<OCIHandle>& hp, int attr, OCIError* errhp)
{
return get_oci_attr<T>(hp.get(), attr, errhp);
}

} // anonymous namespace


Expand Down Expand Up @@ -96,8 +103,56 @@ void oracle_statement_backend::prepare(std::string const &query,

statement_backend::exec_fetch_result oracle_statement_backend::execute(int number)
{
ub4 mode = OCI_DEFAULT;

// We want to use OCI_BATCH_ERRORS for bulk operations in order to get
// information about the row(s) which resulted in errors.
if (hasVectorUseElements_)
{
switch (get_statement_attr<ub2>(OCI_ATTR_STMT_TYPE))
{
case OCI_STMT_UPDATE:
case OCI_STMT_DELETE:
case OCI_STMT_INSERT:
mode = OCI_BATCH_ERRORS;
break;
}
}

sword res = OCIStmtExecute(session_.svchp_, stmtp_, session_.errhp_,
static_cast<ub4>(number), 0, 0, 0, OCI_DEFAULT);
static_cast<ub4>(number), 0, 0, 0, mode);

// For bulk operations, "success with info" is used even when some rows
// resulted in errors, so check for this and return error in this case.
if (hasVectorUseElements_ && res == OCI_SUCCESS_WITH_INFO)
{
// We don't handle multiple errors and only distinguish between not
// having any errors at all and having at least one. This is, of
// course, not ideal, but provides at least some information about the
// (first) error.
//
// Note that we have to use a different error handle to this call to
// avoid clobbering the error handle used for the statement itself.
handle<OCIError> errhTmp(session_.envhp_);
if (get_oci_attr<ub4>(stmtp_, OCI_ATTR_NUM_DML_ERRORS, errhTmp))
{
handle<OCIError> errhRow(session_.envhp_);
sword res2 = OCIParamGet(session_.errhp_,
OCI_HTYPE_ERROR,
errhTmp,
errhRow.ptr(),
0
);
if (res2 != OCI_SUCCESS)
{
throw_oracle_soci_error(res2, errhTmp);
}

error_row_ = get_oci_attr<ub4>(errhRow, OCI_ATTR_DML_ROW_OFFSET, errhTmp);
throw_oracle_soci_error(res, errhRow);
}
//else: No errors, handle as success below.
}

if (res == OCI_SUCCESS || res == OCI_SUCCESS_WITH_INFO)
{
Expand Down

0 comments on commit a4188c8

Please sign in to comment.