Skip to content

Commit

Permalink
Fix binding of parameters of custom types in ODBC backend.
Browse files Browse the repository at this point in the history
Call SQLBindParameter() from pre_use() which is guaranteed to be called when
the input parameters have their correct, final values, notably after they were
converted from custom type by conversion_use_type::convert_to_base().

This also fixes another serious problem for string types in the ODBC backend:
setting a "use" parameter after define_and_bind() but before execute() doesn't
result in buffer overflow any more.

Signed-off-by: Vadim Zeitlin <vz-soci@zeitlins.org>
  • Loading branch information
vadz committed Jul 6, 2012
1 parent eba063a commit d41e031
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 87 deletions.
16 changes: 10 additions & 6 deletions src/backends/odbc/soci-odbc.h
Expand Up @@ -99,12 +99,7 @@ struct odbc_vector_into_type_backend : details::vector_into_type_backend
struct odbc_standard_use_type_backend : details::standard_use_type_backend
{
odbc_standard_use_type_backend(odbc_statement_backend &st)
: statement_(st), data_(0), buf_(0), indHolder_(0) {}

void prepare_for_bind(void *&data, SQLLEN &size,
SQLSMALLINT &sqlType, SQLSMALLINT &cType);
void bind_helper(int &position,
void *data, details::exchange_type type);
: statement_(st), position_(-1), data_(0), buf_(0), indHolder_(0) {}

virtual void bind_by_pos(int &position,
void *data, details::exchange_type type, bool readOnly);
Expand All @@ -116,7 +111,16 @@ struct odbc_standard_use_type_backend : details::standard_use_type_backend

virtual void clean_up();

// Return the pointer to the buffer containing data to be used by ODBC.
// This can be either data_ itself or buf_, that is allocated by this
// function if necessary.
//
// Also fill in the size of the data and SQL and C types of it.
void* prepare_for_bind(SQLLEN &size,
SQLSMALLINT &sqlType, SQLSMALLINT &cType);

odbc_statement_backend &statement_;
int position_;
void *data_;
details::exchange_type type_;
char *buf_;
Expand Down
8 changes: 0 additions & 8 deletions src/backends/odbc/standard-into-type.cpp
Expand Up @@ -52,14 +52,6 @@ void odbc_standard_into_type_backend::define_by_pos(
odbcType_ = SQL_C_ULONG;
size = sizeof(unsigned long);
break;
case x_long_long:
odbcType_ = SQL_C_SBIGINT;
size = sizeof(long long);
break;
case x_unsigned_long_long:
odbcType_ = SQL_C_UBIGINT;
size = sizeof(unsigned long long);
break;
case x_double:
odbcType_ = SQL_C_DOUBLE;
size = sizeof(double);
Expand Down
127 changes: 54 additions & 73 deletions src/backends/odbc/standard-use-type.cpp
Expand Up @@ -14,8 +14,8 @@
using namespace soci;
using namespace soci::details;

void odbc_standard_use_type_backend::prepare_for_bind(
void *&data, SQLLEN &size, SQLSMALLINT &sqlType, SQLSMALLINT &cType)
void* odbc_standard_use_type_backend::prepare_for_bind(
SQLLEN &size, SQLSMALLINT &sqlType, SQLSMALLINT &cType)
{
switch (type_)
{
Expand Down Expand Up @@ -51,20 +51,18 @@ void odbc_standard_use_type_backend::prepare_for_bind(
size = sizeof(double);
break;

// cases that require adjustments and buffer management
case x_char:
sqlType = SQL_CHAR;
cType = SQL_C_CHAR;
size = sizeof(char) + 1;
size = 2;
buf_ = new char[size];
data = buf_;
buf_[0] = *static_cast<char*>(data_);
buf_[1] = '\0';
indHolder_ = SQL_NTS;
break;
case x_stdstring:
{
// TODO: No textual value is assigned here!

std::string* s = static_cast<std::string*>(data);
std::string* s = static_cast<std::string*>(data_);
#ifdef SOCI_ODBC_VERSION_3_IS_TO_BE_CHECKED
sqlType = SQL_VARCHAR;
#else
Expand All @@ -73,21 +71,35 @@ void odbc_standard_use_type_backend::prepare_for_bind(
sqlType = SQL_LONGVARCHAR;
#endif
cType = SQL_C_CHAR;
size = s->size() + 1;
buf_ = new char[size];
data = buf_;
size = s->size();
buf_ = new char[size+1];
memcpy(buf_, s->c_str(), size);
buf_[size++] = '\0';
indHolder_ = SQL_NTS;
}
break;
case x_stdtm:
{
std::tm *t = static_cast<std::tm *>(data_);

sqlType = SQL_TIMESTAMP;
cType = SQL_C_TIMESTAMP;
buf_ = new char[sizeof(TIMESTAMP_STRUCT)];
data = buf_;
size = 19; // This number is not the size in bytes, but the number
// of characters in the date if it was written out
// yyyy-mm-dd hh:mm:ss
break;

TIMESTAMP_STRUCT * ts = reinterpret_cast<TIMESTAMP_STRUCT*>(buf_);

ts->year = static_cast<SQLSMALLINT>(t->tm_year + 1900);
ts->month = static_cast<SQLUSMALLINT>(t->tm_mon + 1);
ts->day = static_cast<SQLUSMALLINT>(t->tm_mday);
ts->hour = static_cast<SQLUSMALLINT>(t->tm_hour);
ts->minute = static_cast<SQLUSMALLINT>(t->tm_min);
ts->second = static_cast<SQLUSMALLINT>(t->tm_sec);
ts->fraction = 0;
}
break;

case x_blob:
{
Expand All @@ -106,31 +118,13 @@ void odbc_standard_use_type_backend::prepare_for_bind(
break;
case x_statement:
case x_rowid:
break;
}
}

void odbc_standard_use_type_backend::bind_helper(int &position, void *data, exchange_type type)
{
data_ = data; // for future reference
type_ = type; // for future reference

SQLSMALLINT sqlType;
SQLSMALLINT cType;
SQLLEN size;

prepare_for_bind(data, size, sqlType, cType);

SQLRETURN rc = SQLBindParameter(statement_.hstmt_,
static_cast<SQLUSMALLINT>(position++),
SQL_PARAM_INPUT,
cType, sqlType, size, 0, data, 0, &indHolder_);

if (is_odbc_error(rc))
{
throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_,
"Binding");
// Unsupported data types.
return NULL;
}

// Return either the pointer to C++ data itself or the buffer that we
// allocated, if any.
return buf_ ? buf_ : data_;
}

void odbc_standard_use_type_backend::bind_by_pos(
Expand All @@ -142,7 +136,9 @@ void odbc_standard_use_type_backend::bind_by_pos(
"Binding for use elements must be either by position or by name.");
}

bind_helper(position, data, type);
position_ = position++;
data_ = data;
type_ = type;

statement_.boundByPos_ = true;
}
Expand Down Expand Up @@ -170,54 +166,39 @@ void odbc_standard_use_type_backend::bind_by_name(
count++;
}

if (position != -1)
{
bind_helper(position, data, type);
}
else
if (position == -1)
{
std::ostringstream ss;
ss << "Unable to find name '" << name << "' to bind to";
throw soci_error(ss.str().c_str());
}

position_ = position;
data_ = data;
type_ = type;

statement_.boundByName_ = true;
}

void odbc_standard_use_type_backend::pre_use(indicator const *ind)
{
// first deal with data
if (type_ == x_char)
{
char *c = static_cast<char*>(data_);
buf_[0] = *c;
buf_[1] = '\0';
}
else if (type_ == x_stdstring)
{
std::string *s = static_cast<std::string *>(data_);
std::size_t const bufSize = s->size() + 1;
// TODO: this is a hack (for buffer re-size? --mloskot)
//delete [] buf_;
//buf_ = new char[bufSize];

std::size_t const sSize = s->size();
std::size_t const toCopy = sSize < bufSize -1 ? sSize + 1 : bufSize - 1;
strncpy(buf_, s->c_str(), toCopy);
buf_[toCopy] = '\0';
}
else if (type_ == x_stdtm)
{
std::tm *t = static_cast<std::tm *>(data_);
TIMESTAMP_STRUCT * ts = reinterpret_cast<TIMESTAMP_STRUCT*>(buf_);
SQLSMALLINT sqlType;
SQLSMALLINT cType;
SQLLEN size;

ts->year = static_cast<SQLSMALLINT>(t->tm_year + 1900);
ts->month = static_cast<SQLUSMALLINT>(t->tm_mon + 1);
ts->day = static_cast<SQLUSMALLINT>(t->tm_mday);
ts->hour = static_cast<SQLUSMALLINT>(t->tm_hour);
ts->minute = static_cast<SQLUSMALLINT>(t->tm_min);
ts->second = static_cast<SQLUSMALLINT>(t->tm_sec);
ts->fraction = 0;
void* const sqlData = prepare_for_bind(size, sqlType, cType);

SQLRETURN rc = SQLBindParameter(statement_.hstmt_,
static_cast<SQLUSMALLINT>(position_),
SQL_PARAM_INPUT,
cType, sqlType, size, 0,
sqlData, 0, &indHolder_);

if (is_odbc_error(rc))
{
throw odbc_soci_error(SQL_HANDLE_STMT, statement_.hstmt_,
"Binding");
}

// then handle indicators
Expand Down

0 comments on commit d41e031

Please sign in to comment.