Skip to content

Commit

Permalink
Merge branch 'error-msg-bulk-insert'
Browse files Browse the repository at this point in the history
Show vector parameters values in error messages.

See #1200.
  • Loading branch information
vadz committed Feb 4, 2025
2 parents 72f9616 + 0ea3281 commit b7b470e
Show file tree
Hide file tree
Showing 18 changed files with 441 additions and 110 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Changes affecting all or multiple backends:
- Fix several problems detected by UBSAN and use it in the CI builds too now.
- Improvements to connection string handling (#1176).
- Fix get_affected_rows() after partial updates for ODBC and SQLite (#1199).
- Show values of vector parameters in the error messages (#1200).

Backend-specific changes:

Expand Down
9 changes: 9 additions & 0 deletions include/soci/firebird/soci-firebird.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ struct SOCI_FIREBIRD_DECL firebird_statement_backend : details::statement_backen
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 current_row_; }

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

Expand Down Expand Up @@ -231,6 +232,9 @@ struct SOCI_FIREBIRD_DECL firebird_statement_backend : details::statement_backen

long long rowsAffectedBulk_; // number of rows affected by the last bulk operation

// Return the number of rows affected by last operation.
long long get_last_row_count();

virtual void exchangeData(bool gotData, int row);
virtual void prepareSQLDA(XSQLDA ** sqldap, short size = 10);
virtual void rewriteQuery(std::string const & query,
Expand All @@ -249,6 +253,11 @@ struct SOCI_FIREBIRD_DECL firebird_statement_backend : details::statement_backen
std::map <std::string, int> names_;

bool procedure_;

private:
// Used during bulk operations to keep track of the row which potentially
// resulted in an error.
int current_row_ = -1;
};

struct SOCI_FIREBIRD_DECL firebird_blob_backend : details::blob_backend
Expand Down
4 changes: 4 additions & 0 deletions include/soci/odbc/soci-odbc.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ struct SOCI_ODBC_DECL odbc_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 @@ -299,6 +300,9 @@ struct SOCI_ODBC_DECL odbc_statement_backend : details::statement_backend
private:
// fetch() helper wrapping SQLFetch() call for the given range of rows.
exec_fetch_result do_fetch(int beginRow, int endRow);

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

struct SOCI_ODBC_DECL odbc_rowid_backend : details::rowid_backend
Expand Down
9 changes: 9 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 All @@ -248,6 +249,14 @@ struct SOCI_ORACLE_DECL oracle_statement_backend : details::statement_backend
bool boundByName_;
bool boundByPos_;
bool noData_;

private:
// 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
5 changes: 5 additions & 0 deletions include/soci/postgresql/soci-postgresql.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ struct SOCI_POSTGRESQL_DECL postgresql_statement_backend : details::statement_ba
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 current_row_; }

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

Expand Down Expand Up @@ -311,6 +312,10 @@ struct SOCI_POSTGRESQL_DECL postgresql_statement_backend : details::statement_ba
// type queries with custom types
typedef std::unordered_map<unsigned long, char> CategoryByColumnOID;
CategoryByColumnOID categoryByColumnOID_;

private:
// Current row during a bulk operation or -1 if it's not in progress.
int current_row_ = -1;
};

struct SOCI_POSTGRESQL_DECL postgresql_rowid_backend : details::rowid_backend
Expand Down
7 changes: 7 additions & 0 deletions include/soci/soci-backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,13 @@ class statement_backend

virtual std::string get_parameter_name(int index) const = 0;

// This function returns the index of the row which is used when dumping
// the value of query parameters for bulk operations. If it is -1, which is
// the default value returned by it, no specific row is shown, but if a
// backend can determine the row of interest, e.g. the first row which
// triggered an error during execute(), it should return its index.
virtual int get_row_to_dump() const { return -1; }

virtual std::string rewrite_for_procedure_call(std::string const& query) = 0;

virtual int prepare_for_describe() = 0;
Expand Down
7 changes: 7 additions & 0 deletions include/soci/sqlite3/soci-sqlite3.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ struct SOCI_SQLITE3_DECL sqlite3_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 current_row_; }

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

Expand All @@ -247,6 +248,12 @@ struct SOCI_SQLITE3_DECL sqlite3_statement_backend : details::statement_backend
long long rowsAffectedBulk_; // number of rows affected by the last bulk operation

private:
// This is used in bind_and_execute(), called by execute() for bulk
// operations and allows to show the values corresponding to the last
// processed row in the query context generated by calling dump_value()
// using this row number if an error occurs.
int current_row_ = -1;

exec_fetch_result load_rowset(int totalRows);
exec_fetch_result load_one();
exec_fetch_result bind_and_execute(int number);
Expand Down
6 changes: 3 additions & 3 deletions include/soci/use-type.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class SOCI_DECL use_type_base

virtual void bind(statement_impl & st, int & position) = 0;
virtual std::string get_name() const = 0;
virtual void dump_value(std::ostream& os) const = 0;
virtual void dump_value(std::ostream& os, int index) const = 0;
virtual void pre_exec(int num) = 0;
virtual void pre_use() = 0;
virtual void post_use(bool gotData) = 0;
Expand Down Expand Up @@ -76,7 +76,7 @@ class SOCI_DECL standard_use_type : public use_type_base
~standard_use_type() override;
void bind(statement_impl & st, int & position) override;
std::string get_name() const override { return name_; }
void dump_value(std::ostream& os) const override;
void dump_value(std::ostream& os, int index) const override;
virtual void * get_data() { return data_; }

// conversion hook (from arbitrary user type to base type)
Expand Down Expand Up @@ -157,7 +157,7 @@ class SOCI_DECL vector_use_type : public use_type_base
private:
void bind(statement_impl& st, int & position) override;
std::string get_name() const override { return name_; }
void dump_value(std::ostream& os) const override;
void dump_value(std::ostream& os, int index) const override;
void pre_exec(int num) override;
void pre_use() override;
void post_use(bool) override { /* nothing to do */ }
Expand Down
2 changes: 1 addition & 1 deletion include/soci/values-exchange.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class use_type<values> : public use_type_base
return oss.str();
}

void dump_value(std::ostream& os) const override
void dump_value(std::ostream& os, int /* index */) const override
{
// TODO: Dump all columns.
os << "<value>";
Expand Down
22 changes: 14 additions & 8 deletions src/backends/firebird/statement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,35 +409,36 @@ firebird_statement_backend::execute(int number)

if (useType_ == eVector)
{
long long rowsAffectedBulkTemp = 0;
rowsAffectedBulk_ = 0;

// Here we have to explicitly loop to achieve the
// effect of inserting or updating with vector use elements.
std::size_t rows = static_cast<firebird_vector_use_type_backend*>(uses_[0])->size();
for (std::size_t row=0; row < rows; ++row)
const int rows = static_cast<firebird_vector_use_type_backend*>(uses_[0])->size();
for (current_row_ = 0; current_row_ < rows; ++current_row_)
{
// first we have to prepare input parameters
for (std::size_t col=0; col<usize; ++col)
{
static_cast<firebird_vector_use_type_backend*>(uses_[col])->exchangeData(row);
static_cast<firebird_vector_use_type_backend*>(uses_[col])->exchangeData(current_row_);
}

// then execute query
if (isc_dsql_execute(stat, session_.current_transaction(), &stmtp_, SQL_DIALECT_V6, t))
{
// preserve the number of rows affected so far.
rowsAffectedBulk_ = rowsAffectedBulkTemp;
throw_iscerror(stat);
}
else
{
rowsAffectedBulkTemp += get_affected_rows();
// Don't call get_affected_rows() here, because it would return
// the current value of rowsAffectedBulk_ itself.
rowsAffectedBulk_ += get_last_row_count();
}
// soci does not allow bulk insert/update and bulk select operations
// in same query. So here, we know that into elements are not
// vectors. So, there is no need to fetch data here.
}
rowsAffectedBulk_ = rowsAffectedBulkTemp;

current_row_ = -1;
}
else
{
Expand Down Expand Up @@ -569,6 +570,11 @@ long long firebird_statement_backend::get_affected_rows()
return rowsAffectedBulk_;
}

return get_last_row_count();
}

long long firebird_statement_backend::get_last_row_count()
{
ISC_STATUS_ARRAY stat;
char type_item[] = { isc_info_sql_records };
char res_buffer[256];
Expand Down
10 changes: 6 additions & 4 deletions src/backends/odbc/statement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#define SOCI_ODBC_SOURCE
#include "soci/odbc/soci-odbc.h"
#include "soci/soci-unicode.h"
#include "soci/type-holder.h"

#include <cctype>
#include <sstream>
#include <cstring>
Expand Down Expand Up @@ -175,7 +177,7 @@ odbc_statement_backend::execute(int number)
{
rowsAffected_ = 0;

SQLLEN firstErrorRow = -1;
error_row_ = -1;
for (SQLULEN i = 0; i < rows_processed; ++i)
{
switch (status[i])
Expand All @@ -186,8 +188,8 @@ odbc_statement_backend::execute(int number)
break;

case SQL_PARAM_ERROR:
if (firstErrorRow == -1)
firstErrorRow = i;
if (error_row_ == -1)
error_row_ = soci_cast<int, SQLULEN>::cast(i);
break;

case SQL_PARAM_UNUSED:
Expand All @@ -202,7 +204,7 @@ odbc_statement_backend::execute(int number)
// operation which succeeded for all rows -- even though this
// hasn't been observed so far. In this case, we shouldn't
// throw an error.
if (firstErrorRow == -1)
if (error_row_ == -1)
error = false;
}
else
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
Loading

0 comments on commit b7b470e

Please sign in to comment.