Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show vector parameters values in error messages #1200

Merged
merged 12 commits into from
Feb 4, 2025
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