Permalink
Browse files

Add support for 64 bit integers when using Oracle ODBC driver.

Oracle ODBC driver silently (!) doesn't support SQL_BIGINT ODBC type.

Add a workaround to allow using 64 bit integers with it by passing them
to/from the database as strings.

Signed-off-by: Vadim Zeitlin <vz-soci@zeitlins.org>
  • Loading branch information...
vadz committed Jun 15, 2012
1 parent 22e740a commit b82364ee7b7009667f90eb789f16e19749016c46
Showing with 136 additions and 22 deletions.
  1. +48 −12 src/backends/odbc/soci-odbc.h
  2. +47 −4 src/backends/odbc/standard-into-type.cpp
  3. +41 −6 src/backends/odbc/standard-use-type.cpp
@@ -41,10 +41,34 @@ namespace details
}
struct odbc_statement_backend;
struct odbc_standard_into_type_backend : details::standard_into_type_backend
// Helper of into and use backends.
class odbc_standard_type_backend_base
{
protected:
odbc_standard_type_backend_base(odbc_statement_backend &st)
: statement_(st) {}
// Check if we need to pass 64 bit integers as strings to the database as
// some drivers don't support them directly.
inline bool use_string_for_bigint() const;
// If we do need to use strings for 64 bit integers, this constant defines
// the maximal string length needed.
enum
{
// This is the length of decimal representation of UINT64_MAX + 1.
max_bigint_length = 21
};
odbc_statement_backend &statement_;
};
struct odbc_standard_into_type_backend : details::standard_into_type_backend,
private odbc_standard_type_backend_base
{
odbc_standard_into_type_backend(odbc_statement_backend &st)
: statement_(st), buf_(0)
: odbc_standard_type_backend_base(st), buf_(0)
{}
virtual void define_by_pos(int &position,
@@ -56,7 +80,6 @@ struct odbc_standard_into_type_backend : details::standard_into_type_backend
virtual void clean_up();
odbc_statement_backend &statement_;
char *buf_; // generic buffer
void *data_;
details::exchange_type type_;
@@ -65,10 +88,11 @@ struct odbc_standard_into_type_backend : details::standard_into_type_backend
SQLLEN valueLen_;
};
struct odbc_vector_into_type_backend : details::vector_into_type_backend
struct odbc_vector_into_type_backend : details::vector_into_type_backend,
private odbc_standard_type_backend_base
{
odbc_vector_into_type_backend(odbc_statement_backend &st)
: statement_(st), indHolders_(NULL),
: odbc_standard_type_backend_base(st), indHolders_(NULL),
data_(NULL), buf_(NULL) {}
virtual void define_by_pos(int &position,
@@ -86,7 +110,6 @@ struct odbc_vector_into_type_backend : details::vector_into_type_backend
// (as part of the define_by_pos)
void prepare_indicators(std::size_t size);
odbc_statement_backend &statement_;
SQLLEN *indHolders_;
std::vector<SQLLEN> indHolderVec_;
@@ -97,10 +120,12 @@ struct odbc_vector_into_type_backend : details::vector_into_type_backend
SQLSMALLINT odbcType_;
};
struct odbc_standard_use_type_backend : details::standard_use_type_backend
struct odbc_standard_use_type_backend : details::standard_use_type_backend,
private odbc_standard_type_backend_base
{
odbc_standard_use_type_backend(odbc_statement_backend &st)
: statement_(st), position_(-1), data_(0), buf_(0), indHolder_(0) {}
: odbc_standard_type_backend_base(st),
position_(-1), data_(0), buf_(0), indHolder_(0) {}
virtual void bind_by_pos(int &position,
void *data, details::exchange_type type, bool readOnly);
@@ -120,18 +145,18 @@ struct odbc_standard_use_type_backend : details::standard_use_type_backend
void* prepare_for_bind(SQLLEN &size,
SQLSMALLINT &sqlType, SQLSMALLINT &cType);
odbc_statement_backend &statement_;
int position_;
void *data_;
details::exchange_type type_;
char *buf_;
SQLLEN indHolder_;
};
struct odbc_vector_use_type_backend : details::vector_use_type_backend
struct odbc_vector_use_type_backend : details::vector_use_type_backend,
private odbc_standard_type_backend_base
{
odbc_vector_use_type_backend(odbc_statement_backend &st)
: statement_(st), indHolders_(NULL),
: odbc_standard_type_backend_base(st), indHolders_(NULL),
data_(NULL), buf_(NULL) {}
// helper function for preparing indicators
@@ -154,7 +179,6 @@ struct odbc_vector_use_type_backend : details::vector_use_type_backend
virtual void clean_up();
odbc_statement_backend &statement_;
SQLLEN *indHolders_;
std::vector<SQLLEN> indHolderVec_;
@@ -362,6 +386,18 @@ inline bool is_odbc_error(SQLRETURN rc)
}
}
inline bool odbc_standard_type_backend_base::use_string_for_bigint() const
{
// Oracle ODBC driver doesn't support SQL_C_[SU]BIGINT data types
// (see appendix G.1 of Oracle Database Administrator's reference at
// http://docs.oracle.com/cd/B19306_01/server.102/b15658/app_odbc.htm),
// so we need a special workaround for this case and we represent 64
// bit integers as strings and rely on ODBC driver for transforming
// them to SQL_NUMERIC.
return statement_.session_.get_database_product()
== odbc_session_backend::prod_oracle;
}
struct odbc_backend_factory : backend_factory
{
odbc_backend_factory() {}
@@ -8,10 +8,17 @@
#define SOCI_ODBC_SOURCE
#include "soci-odbc.h"
#include <ctime>
#include <stdio.h> // sscanf()
using namespace soci;
using namespace soci::details;
#ifdef _MSC_VER
#define LL_FMT_FLAGS "I64"
#else
#define LL_FMT_FLAGS "ll"
#endif
void odbc_standard_into_type_backend::define_by_pos(
int & position, void * data, exchange_type type)
@@ -49,12 +56,32 @@ void odbc_standard_into_type_backend::define_by_pos(
size = sizeof(long);
break;
case x_long_long:
odbcType_ = SQL_C_SBIGINT;
size = sizeof(long long);
if (use_string_for_bigint())
{
odbcType_ = SQL_C_CHAR;
size = max_bigint_length;
buf_ = new char[size];
data = buf_;
}
else // Normal case, use ODBC support.
{
odbcType_ = SQL_C_SBIGINT;
size = sizeof(long long);
}
break;
case x_unsigned_long_long:
odbcType_ = SQL_C_UBIGINT;
size = sizeof(unsigned long long);
if (use_string_for_bigint())
{
odbcType_ = SQL_C_CHAR;
size = max_bigint_length;
buf_ = new char[size];
data = buf_;
}
else // Normal case, use ODBC support.
{
odbcType_ = SQL_C_UBIGINT;
size = sizeof(unsigned long long);
}
break;
case x_double:
odbcType_ = SQL_C_DOUBLE;
@@ -153,6 +180,22 @@ void odbc_standard_into_type_backend::post_fetch(
// normalize and compute the remaining fields
std::mktime(t);
}
else if (type_ == x_long_long && use_string_for_bigint())
{
long long *ll = static_cast<long long *>(data_);
if (sscanf(buf_, "%" LL_FMT_FLAGS "d", ll) != 1)
{
throw soci_error("Failed to parse the returned 64-bit integer value");
}
}
else if (type_ == x_unsigned_long_long && use_string_for_bigint())
{
unsigned long long *ll = static_cast<unsigned long long *>(data_);
if (sscanf(buf_, "%" LL_FMT_FLAGS "u", ll) != 1)
{
throw soci_error("Failed to parse the returned 64-bit integer value");
}
}
}
}
@@ -14,6 +14,15 @@
using namespace soci;
using namespace soci::details;
#ifdef _MSC_VER
#pragma warning(disable:4996)
#define snprintf _snprintf
#define LL_FMT_FLAGS "I64"
#else
#define LL_FMT_FLAGS "ll"
#endif
void* odbc_standard_use_type_backend::prepare_for_bind(
SQLLEN &size, SQLSMALLINT &sqlType, SQLSMALLINT &cType)
{
@@ -31,14 +40,40 @@ void* odbc_standard_use_type_backend::prepare_for_bind(
size = sizeof(int);
break;
case x_long_long:
sqlType = SQL_BIGINT;
cType = SQL_C_SBIGINT;
size = sizeof(long long);
if (use_string_for_bigint())
{
sqlType = SQL_NUMERIC;
cType = SQL_C_CHAR;
size = max_bigint_length;
buf_ = new char[size];
snprintf(buf_, size, "%" LL_FMT_FLAGS "d",
*static_cast<long long *>(data_));
indHolder_ = SQL_NTS;
}
else // Normal case, use ODBC support.
{
sqlType = SQL_BIGINT;
cType = SQL_C_SBIGINT;
size = sizeof(long long);
}
break;
case x_unsigned_long_long:
sqlType = SQL_BIGINT;
cType = SQL_C_UBIGINT;
size = sizeof(unsigned long long);
if (use_string_for_bigint())
{
sqlType = SQL_NUMERIC;
cType = SQL_C_CHAR;
size = max_bigint_length;
buf_ = new char[size];
snprintf(buf_, size, "%" LL_FMT_FLAGS "u",
*static_cast<unsigned long long *>(data_));
indHolder_ = SQL_NTS;
}
else // Normal case, use ODBC support.
{
sqlType = SQL_BIGINT;
cType = SQL_C_UBIGINT;
size = sizeof(unsigned long long);
}
break;
case x_double:
sqlType = SQL_DOUBLE;

0 comments on commit b82364e

Please sign in to comment.