#include <windows.h> // Needed for ODBC.
#include <sql.h> // ODBC
#include <sqlext.h> // SQL_OV_ODBC3
#include <utils/ODBC.h> // ourselves.
#include <utils/util.h>
#include <utils/mystring.h> // int_of
using namespace std;
using namespace utils;
namespace utils {
namespace ODBC {
#define FAIL_ON_ERROR(caller_name, handle, func) (handle).fail_on_error((caller_name), func, #func, __FILE__, __LINE__ )
/*****************************************************************************/
/*****************************************************************************/
Handle::Handle( int handle_type)
: h_(SQL_NULL_HANDLE)
,h_type_(SQL_HANDLE_ENV)
,parent_(0)
{
if (handle_type == SQL_HANDLE_ENV) {
SQLRETURN r = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &h_);
if (!SQL_SUCCEEDED(r)) {
throw Error("ODBC Error: Unable to allocate environment handle!");
}
SQLINTEGER version = SQL_OV_ODBC3;
try {
FAIL_ON_ERROR("ODBC::Handle", *this, SQLSetEnvAttr(h_, SQL_ATTR_ODBC_VERSION, (void*)(version), sizeof(version)));
} catch (exception&) {
SQLFreeHandle(h_type_, h_);
throw; // rethrow...
}
} else {
throw Error("ODBC::Handle: invalid parameter (%d) specified for environment handle!", handle_type);
}
}
/*****************************************************************************/
Handle::Handle( int handle_type,
Handle& parent)
: h_(SQL_NULL_HANDLE)
,h_type_(handle_type)
,parent_(0)
{
if (handle_type==SQL_HANDLE_DBC || handle_type==SQL_HANDLE_STMT || handle_type==SQL_HANDLE_DESC) {
FAIL_ON_ERROR("ODBC::Handle", parent, SQLAllocHandle(handle_type, parent(), &h_));
parent.childs_.push_front(this);
parent_ = &parent;
} else {
throw Error("ODBC::Handle: invalid parameter (%d) specified for environment handle!", handle_type);
}
}
/*****************************************************************************/
void*
Handle::operator()()
{
return h_;
}
/*****************************************************************************/
void*
Handle::handle()
{
return h_;
}
/*****************************************************************************/
Handle::~Handle()
{
close();
}
/*****************************************************************************/
void
Handle::close()
{
// Close all childs.
while (!childs_.empty()) {
Handle* ch = childs_.front();
childs_.pop_front();
ch->close();
}
// Close ourselves.
if (h_ != SQL_NULL_HANDLE) {
SQLFreeHandle(h_type_, h_);
}
// Unregister us with the parents.
if (parent_!=0) {
parent_->childs_.remove(this);
}
}
/*****************************************************************************/
void
Handle::fail_on_error( const char* caller_name,
int r,
const char* call_name,
const char* filename,
const unsigned int lineno)
{
if (!SQL_SUCCEEDED(r)) {
SQLCHAR sqlstate[10];
SQLINTEGER native_r;
SQLCHAR message[1024];
SQLSMALLINT message_size;
memset(sqlstate, 0, sizeof(sqlstate));
memset(message, 0, sizeof(message));
string real_call_name(call_name);
{
string::size_type ppos = real_call_name.find('(');
if (ppos != string::npos) {
real_call_name.resize(ppos);
}
}
string real_filename(filename);
{
string::size_type ppos = real_filename.rfind('\\');
if (ppos != string::npos) {
for (unsigned int i=ppos + 1; i<real_filename.size(); ++i) {
real_filename[i - ppos - 1] = real_filename[i];
}
real_filename.resize(real_filename.size() - ppos - 1);
}
}
const int rr = SQLGetDiagRec(h_type_, h_,
1,
sqlstate,
&native_r,
message,
sizeof(message),
&message_size);
if (SQL_SUCCEEDED(rr)) {
throw Error("%s: ODBC failed, error: %s, sql state: %s, call: %s, file %s, line %d.",
caller_name, message, sqlstate, real_call_name.c_str(), real_filename.c_str(), lineno);
} else {
throw Error("%s: ODBC failed for unknown reasons, call: %s", caller_name, call_name);
}
}
}
/*****************************************************************************/
/*****************************************************************************/
Parameter::Parameter( const TYPE type,
Parameter* next)
: type_(type)
,direction_(DIRECTION_IN)
,param_int_(0)
,param_int64_(0)
,param_float_(0)
,next_(next)
{
}
/*****************************************************************************/
void
Parameter::bind_parameter( Handle& statement,
const unsigned int number)
{
SQLSMALLINT direction = direction_==DIRECTION_IN ? SQL_PARAM_INPUT : SQL_PARAM_OUTPUT;
SQLSMALLINT value_type = 0; // C type
SQLSMALLINT parameter_type = 0; // SQL type.
SQLUINTEGER column_size = 0;
SQLSMALLINT decimal_digits = 0;
SQLPOINTER parameter_value = 0; // Pointer to data.
SQLINTEGER buffer_length = 0; // Data length.
bind_return_ = 0;
switch (type_) {
case TYPE_INT_NULL:
value_type = SQL_C_SLONG;
parameter_type = SQL_INTEGER;
parameter_value = ¶m_int_;
bind_return_ = SQL_NULL_DATA;
case TYPE_INT:
value_type = SQL_C_SLONG;
parameter_type = SQL_INTEGER;
parameter_value = ¶m_int_;
break;
case TYPE_INT64:
value_type = SQL_C_SBIGINT;
parameter_type = SQL_BIGINT;
parameter_value = ¶m_int64_;
break;
case TYPE_DOUBLE:
value_type = SQL_C_DOUBLE;
parameter_type = SQL_DOUBLE;
parameter_value = ¶m_float_;
break;
case TYPE_DOUBLE_NULL:
value_type = SQL_C_DOUBLE;
parameter_type = SQL_DOUBLE;
parameter_value = ¶m_float_;
bind_return_ = SQL_NULL_DATA;
break;
case TYPE_STRING:
value_type = SQL_C_CHAR;
parameter_type = SQL_CHAR;
if (direction_ == DIRECTION_IN) {
column_size = param_string_.size();
parameter_value = const_cast<char*>(param_string_.c_str());
} else {
column_size = param_string_out_->size();
parameter_value = const_cast<char*>(param_string_out_->c_str());
buffer_length = param_string_out_->size();
}
bind_return_ = SQL_NTS;
break;
case TYPE_TIMESTAMP:
value_type = SQL_C_TYPE_TIMESTAMP;
parameter_type = SQL_TYPE_TIMESTAMP;
parameter_value = ¶m_timestamp_[0];
break;
}
FAIL_ON_ERROR("ODBC::Parameter", statement,
SQLBindParameter(statement(), number, direction, value_type, parameter_type,
column_size, decimal_digits, parameter_value, buffer_length, &bind_return_));
}
/*****************************************************************************/
void
Parameter::update_output()
{
if (direction_ == DIRECTION_OUT) {
switch (type_) {
case TYPE_STRING:
{
const char* ptr = param_string_out_->c_str();
const unsigned int length = strlen(ptr);
param_string_out_->resize(length);
}
break;
}
}
if (next_.get() != 0) {
next_->update_output();
}
}
/*****************************************************************************/
Parameter*
makeParameterInt( const int param,
Parameter* next)
{
Parameter* r = new Parameter(Parameter::TYPE_INT, next);
r->param_int_ = param;
return r;
}
/*****************************************************************************/
Parameter*
makeParameterInt64( const __int64 param,
Parameter* next)
{
Parameter* r = new Parameter(Parameter::TYPE_INT64, next);
r->param_int64_ = param;
return r;
}
/*****************************************************************************/
Parameter*
makeParameterFloat( const double param,
Parameter* next)
{
Parameter* r = new Parameter(Parameter::TYPE_DOUBLE, next);
r->param_float_ = param;
return r;
}
/*****************************************************************************/
Parameter*
makeParameterFloat( const utils::Option<double>& param,
Parameter* next)
{
if (param.some()) {
Parameter* r = new Parameter(Parameter::TYPE_DOUBLE, next);
r->param_float_ = param();
return r;
} else {
Parameter* r = new Parameter(Parameter::TYPE_DOUBLE_NULL, next);
return r;
}
}
/*****************************************************************************/
Parameter*
makeParameterString(const std::string& param,
Parameter* next)
{
Parameter* r = new Parameter(Parameter::TYPE_STRING, next);
r->param_string_ = param;
return r;
}
/*****************************************************************************/
Parameter*
makeParameterStringOut(
std::string& param,
const unsigned int max_length,
Parameter* next)
{
Parameter* r = new Parameter(Parameter::TYPE_STRING, next);
r->direction_ = Parameter::DIRECTION_OUT;
r->param_string_out_ = ¶m;
r->param_string_out_->resize(max_length);
return r;
}
/*****************************************************************************/
Parameter*
makeParameterTimestamp( const utils::my_time& param,
Parameter* next)
{
Parameter* r = new Parameter(Parameter::TYPE_TIMESTAMP, next);
SQL_TIMESTAMP_STRUCT* ts = reinterpret_cast<SQL_TIMESTAMP_STRUCT*>(&r->param_timestamp_[0]);
ts->year = param.year;
ts->month = param.month;
ts->day = param.day;
ts->hour = param.hour;
ts->minute = param.minute;
ts->second = param.second;
ts->fraction= param.millisecond * 1000 * 1000;
return r;
}
/*****************************************************************************/
/*****************************************************************************/
Statement::BindColumn::BindColumn( const BIND _bind_type)
: bind_type(_bind_type)
,ptr_int(0)
,ptr_int64(0)
,ptr_float(0)
,ptr_float_null(0)
,ptr_string(0)
,ptr_timestamp(0)
,buffer_read_(0)
{
}
/*****************************************************************************/
Statement::Statement( Handle& connection,
const std::string& query,
TYPE type)
:
statement_(new Handle(SQL_HANDLE_STMT, connection))
,query_(query)
,type_(type)
,param_count_(0)
,columns_fetched_(false)
{
switch (type_) {
case TYPE_SINGLE:
// Execute the given query. */
FAIL_ON_ERROR("ODBC::Statement", *statement_,
SQLExecDirect(statement_->handle(), (unsigned char*)(query.c_str()), query.size()));
break;
case TYPE_PREPARE:
// Prepare the statement.
FAIL_ON_ERROR("ODBC::Statement", *statement_,
SQLPrepare(statement_->handle(), (unsigned char*)(query.c_str()), query.size()));
// Count number of parameters.
{
unsigned int i;
for (i=0; i<query.size(); ++i) {
if (query[i] == '?') {
++param_count_;
}
}
}
break;
}
}
/*****************************************************************************/
void
Statement::update_bindings_()
{
list<BindColumn*>::iterator it;
for (it=bindings_.begin(); it!=bindings_.end(); ++it) {
BindColumn* bc = *it;
switch (bc->bind_type) {
case BIND_INT:
if (bc->buffer_read_ == SQL_NULL_DATA) {
*(bc->ptr_int) = 0;
}
break;
case BIND_INT64:
if (bc->buffer_read_ == SQL_NULL_DATA) {
*(bc->ptr_int64) = 0;
}
break;
case BIND_DOUBLE:
if (bc->buffer_read_ == SQL_NULL_DATA) {
*(bc->ptr_float) = 0;
}
break;
case BIND_DOUBLE_NULL:
if (bc->buffer_read_ == SQL_NULL_DATA) {
*(bc->ptr_float_null) = utils::Option<double>();
} else {
*(bc->ptr_float_null) = utils::Option<double>(*reinterpret_cast<double*>(&bc->buffer[0]));
}
break;
case BIND_STRING:
if (bc->buffer_read_ == SQL_NULL_DATA || bc->buffer_read_<0) {
bc->ptr_string->resize(0);
} else {
const unsigned int n = bc->buffer_read_;
string& s = *(bc->ptr_string);
vector<char>& buffer = bc->buffer;
s.resize(n);
for (unsigned int i=0; i<n; ++i) {
s[i] = buffer[i];
}
}
break;
case BIND_TIMESTAMP:
memset(bc->ptr_timestamp, 0, sizeof(utils::my_time));
if (bc->buffer_read_ != SQL_NULL_DATA) {
const SQL_TIMESTAMP_STRUCT& sql_ts = *reinterpret_cast<SQL_TIMESTAMP_STRUCT*>(&bc->buffer[0]);
my_time& ts = *bc->ptr_timestamp;
ts.year = sql_ts.year;
ts.month = sql_ts.month;
ts.day = sql_ts.day;
ts.hour = sql_ts.hour;
ts.minute = sql_ts.minute;
ts.second = sql_ts.second;
ts.millisecond = sql_ts.fraction / 1000 / 1000;
}
break;
}
}
}
/*****************************************************************************/
Statement::~Statement()
{
// Free \c bindings_.
while (!bindings_.empty()) {
BindColumn* bc = bindings_.front();
bindings_.pop_front();
xdelete(bc);
}
}
/*****************************************************************************/
void
Statement::bind( const ColumnIndex& column,
int& data)
{
BindColumn* r = new BindColumn(BIND_INT);
r->ptr_int = &data;
try {
FAIL_ON_ERROR("ODBC::bind", *statement_,
SQLBindCol(statement_->handle(), column(), SQL_C_SLONG, r->ptr_int, 0, &r->buffer_read_));
} catch (const std::exception&) {
xdelete(r);
throw;
}
bindings_.push_front(r);
}
/*****************************************************************************/
void
Statement::bind( const ColumnIndex& column,
unsigned int& data)
{
bind(column, reinterpret_cast<int&>(data));
}
/*****************************************************************************/
void
Statement::bind( const ColumnIndex& column,
__int64& data)
{
BindColumn* r = new BindColumn(BIND_INT64);
r->ptr_int64 = &data;
try {
FAIL_ON_ERROR("ODBC::bind", *statement_,
SQLBindCol(statement_->handle(), column(), SQL_C_SBIGINT, r->ptr_int64, 0, &r->buffer_read_));
} catch (const std::exception&) {
xdelete(r);
throw;
}
bindings_.push_front(r);
}
/*****************************************************************************/
void
Statement::bind( const ColumnIndex& column,
double& data)
{
BindColumn* r = new BindColumn(BIND_DOUBLE);
r->ptr_float = &data;
try {
FAIL_ON_ERROR("ODBC::bind", *statement_,
SQLBindCol(statement_->handle(), column(), SQL_C_DOUBLE, r->ptr_float, 0, &r->buffer_read_));
} catch (const std::exception&) {
xdelete(r);
throw;
}
bindings_.push_front(r);
}
/*****************************************************************************/
void
Statement::bind( const ColumnIndex& column,
utils::Option<double>& data)
{
BindColumn* r = new BindColumn(BIND_DOUBLE_NULL);
r->ptr_float_null = &data;
r->buffer.resize(sizeof(double));
try {
FAIL_ON_ERROR("ODBC::bind", *statement_,
SQLBindCol(statement_->handle(), column(), SQL_C_DOUBLE, &r->buffer[0], 0, &r->buffer_read_));
} catch (const std::exception&) {
xdelete(r);
throw;
}
bindings_.push_front(r);
}
/*****************************************************************************/
void
Statement::bind( const ColumnIndex& column,
std::string& data,
const int max_length)
{
BindColumn* r = new BindColumn(BIND_STRING);
r->ptr_string = &data;
r->buffer.resize(max_length+1);
try {
FAIL_ON_ERROR("ODBC::bind", *statement_,
SQLBindCol(statement_->handle(), column(), SQL_C_CHAR, &r->buffer[0], r->buffer.size(), &r->buffer_read_));
} catch (const std::exception&) {
xdelete(r);
throw;
}
bindings_.push_front(r);
}
/*****************************************************************************/
void
Statement::bind( const ColumnIndex& column,
utils::my_time& data)
{
BindColumn* r = new BindColumn(BIND_TIMESTAMP);
r->ptr_timestamp = &data;
r->buffer.resize(sizeof(SQL_TIMESTAMP_STRUCT));
try {
FAIL_ON_ERROR("ODBC::bind", *statement_,
SQLBindCol(statement_->handle(), column(), SQL_C_TIMESTAMP, &r->buffer[0], 0, &r->buffer_read_));
} catch (const std::exception&) {
xdelete(r);
throw;
}
bindings_.push_front(r);
}
/*****************************************************************************/
void
Statement::fetch_column_info()
{
if (columns_fetched_) {
return;
} else {
columns_fetched_ = true;
// Number of results.
SQLSMALLINT ncol;
FAIL_ON_ERROR("ODBC::NumColumns", *statement_,
SQLNumResultCols(statement_->handle(),
&ncol));
// Any results?
if (ncol>0) {
// Get column info.
char info_name[1024];
SQLSMALLINT info_namelength;
SQLSMALLINT info_datatype;
SQLUINTEGER info_colsize;
SQLSMALLINT info_decimaldigits;
SQLSMALLINT info_nullable;
column_info.resize(ncol);
for (int i=0; i<ncol; ++i) {
FAIL_ON_ERROR("ODBC::ColumnInfo", *statement_,
SQLDescribeCol(statement_->handle(),
i+1,
reinterpret_cast<unsigned char*>(info_name), sizeof(info_name), &info_namelength,
&info_datatype,
&info_colsize,
&info_decimaldigits,
&info_nullable));
ColumnInfo& ci = column_info[i];
ci.name = info_name;
ci.data_type = info_datatype;
if (info_colsize>0) {
ci.size = info_colsize;
}
if (info_decimaldigits>0) {
ci.decimal_digits = info_decimaldigits;
}
switch (info_nullable) {
case SQL_NO_NULLS:
ci.nullable = false;
break;
case SQL_NULLABLE:
ci.nullable = true;
break;
}
}
} else {
throw Error("Statement: query returned no columns.");
}
}
}
/*****************************************************************************/
bool
Statement::fetch()
{
SQLRETURN r = SQLFetch(statement_->handle());
const bool ok = SQL_SUCCEEDED(r);
if (ok) {
update_bindings_();
}
return ok;
}
/*****************************************************************************/
bool
Statement::fetch( std::vector<std::string>& row)
{
fetch_column_info();
SQLRETURN r = SQLFetch(statement_->handle());
if (SQL_SUCCEEDED(r)) {
// Update bindings, if any.
update_bindings_();
// Prepare...
const unsigned int ncols = column_info.size();
char colbuffer
[2048];
// hopefully not longer 
SQLINTEGER colsize;
row.resize(ncols);
// and SQLGetData All.
for (unsigned int ColumnIndex=0; ColumnIndex<ncols; ++ColumnIndex) {
colsize = 0;
FAIL_ON_ERROR("ODBC::fetch", *statement_,
SQLGetData(statement_->handle(),
ColumnIndex+1,
SQL_C_CHAR,
colbuffer,
sizeof(colbuffer),
&colsize));
colbuffer[colsize] = 0;
row[ColumnIndex] = colbuffer;
}
return true;
}
return false;
}
/*****************************************************************************/
void
Statement::execute( Parameter* params)
{
// Automatically delete parameters on destroying the statement...
last_params_ = auto_ptr<Parameter>(params);
// Close open cursor, if any.
SQLFreeStmt(statement_->handle(), SQL_CLOSE);
// Check type.
if (type_ != TYPE_PREPARE) {
throw Error("Tried to execute query that has not been prepared.");
}
// Count params and check the numbers.
vector<Parameter*> vparams;
{
for (Parameter* p = params; p!=0; p=p->next_.get()) {
vparams.push_back(p);
}
}
if (vparams.size() != param_count_) {
throw Error("Number of parameters given (%d) doesn't match number of parameters in prepared query (%d)!",
vparams.size(), param_count_);
}
// Bind parameters, if any.
unsigned int ParamIndex;
for (ParamIndex=0; ParamIndex<vparams.size(); ++ParamIndex) {
Parameter* param = vparams[ParamIndex];
param->bind_parameter(*statement_, ParamIndex+1);
}
// Execute prepared query.
FAIL_ON_ERROR("ODBC::execute", *statement_, SQLExecute(statement_->handle()));
// Update output parameters, if any.
if (params != 0) {
params->update_output();
}
}
/*****************************************************************************/
int
Statement::query1_int( Parameter* params)
{
execute(params);
vector<string> row;
if (fetch(row)) {
return int_of(row[0]);
}
throw Error("query1_int: Expected at least one row, got none.");
return -1;
}
/*****************************************************************************/
/*****************************************************************************/
Database::Database( const std::string& dsn,
const std::string& username,
const std::string& password)
: dsn_(dsn)
{
// Create handles.
env_ = auto_ptr<Handle>(new Handle(SQL_HANDLE_ENV));
conn_ = auto_ptr<Handle>(new Handle(SQL_HANDLE_DBC, *env_));
// Connect.
FAIL_ON_ERROR("ODBC::Database", *conn_,
SQLConnect(conn_->handle(),
(unsigned char*)(dsn.c_str()), dsn.size(),
(unsigned char*)(username.c_str()), username.size(),
(unsigned char*)(password.c_str()), password.size()
));
// gdblog("ODBC", "Opened data source \"%s\", username \"%s\"", dsn.c_str(), username.c_str());
}
/*****************************************************************************/
std::string
Database::dsn() const
{
return dsn_;
}
/*****************************************************************************/
void
Database::execute( const std::string& query)
{
TRACE_PRINT("ODBC", ("Execute: %s", query.c_str()));
Handle stmt(SQL_HANDLE_STMT, *conn_);
FAIL_ON_ERROR("ODBC::execute", *conn_,
SQLExecDirect(stmt(), (unsigned char*)(query.c_str()), query.size()));
}
/*****************************************************************************/
std::auto_ptr<Statement>
Database::query( const std::string& query)
{
TRACE_PRINT("ODBC", ("Query: %s", query.c_str()));
return auto_ptr<Statement>(new Statement(*conn_, query, Statement::TYPE_SINGLE));
}
/*****************************************************************************/
int
Database::query1_int( const std::string& query)
{
auto_ptr<Statement> cursor(this->query(query));
vector<string> row;
if (cursor->fetch(row)) {
return int_of(row[0]);
}
throw Error("query1_int: Expected at least one row, got none.");
return -1;
}
/*****************************************************************************/
std::auto_ptr<Statement>
Database::prepare( const std::string& query)
{
TRACE_PRINT("ODBC", ("Prepare: %s", query.c_str()));
return auto_ptr<Statement>(new Statement(*conn_, query, Statement::TYPE_PREPARE));
}
/*****************************************************************************/
__int64
Database::last_insert_id()
{
if (last_insert_id_stmt_.get() == 0) {
last_insert_id_stmt_ = prepare("select @@identity");
last_insert_id_stmt_->bind(1, last_insert_id_);
}
last_insert_id_stmt_->execute();
if (last_insert_id_stmt_->fetch()) {
return last_insert_id_;
} else {
conn_->fail_on_error("last_insert_id", -1, "SQLFetch @@identity", __FILE__, __LINE__);
}
return -1;
}
} // namespace ODBC
} // namespace utils