* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*
* odbc.cpp
* The implementations of all classes which hide details of the ODBC APIs.
*
* IDENTIFICATION
* Code/src/gausskernel/cbb/extension/connector/odbc.cpp
*
* -------------------------------------------------------------------------
*/
#include <algorithm>
#include <cassert>
#include <clocale>
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <ctime>
#include <iomanip>
#include <map>
#include "postgres.h"
#include "knl/knl_variable.h"
#include <sqlext.h>
#include "odbc.h"
#include "odbc_bridge.h"
#include "catalog/pg_type.h"
#define MYODBC_ASSERT(expr) assert(expr)
#ifndef MYODBC_ODBC_VERSION
#ifdef SQL_OV_ODBC3_80
#define MYODBC_ODBC_VERSION SQL_OV_ODBC3_80
#else
#define MYODBC_ODBC_VERSION SQL_OV_ODBC3
#endif
#endif
#define MYODBC_TEXT(s) s
#define MYODBC_FUNC(f) f
#define MYODBC_SQLCHAR SQLCHAR
typedef std::u16string wide_string_type;
#define MYODBC_CODECVT_TYPE std::codecvt_utf8_utf16
typedef wide_string_type::value_type wide_char_t;
#define MYODBC_STRINGIZE_I(text) #text
#define MYODBC_STRINGIZE(text) MYODBC_STRINGIZE_I(text)
* By making all calls to ODBC functions through this macro, we can easily get
* runtime debugging information of which ODBC functions are being called,
* in what order, and with what parameters by defining MYODBC_ODBC_API_DEBUG.
*/
#ifdef MYODBC_ODBC_API_DEBUG
#include <iostream>
#define MYODBC_CALL_RC(FUNC, RC, ...) \
do { \
std::cerr << __FILE__ ":" MYODBC_STRINGIZE(__LINE__) " " MYODBC_STRINGIZE((FUNC)) "(" #__VA_ARGS__ ")" \
<< std::endl; \
RC = (FUNC)(__VA_ARGS__); \
} while (false)
#define MYODBC_CALL(FUNC, ...) \
do { \
std::cerr << __FILE__ ":" MYODBC_STRINGIZE(__LINE__) " " MYODBC_STRINGIZE((FUNC)) "(" #__VA_ARGS__ ")" \
<< std::endl; \
(FUNC)(__VA_ARGS__); \
} while (false)
#else
#define MYODBC_CALL_RC(FUNC, RC, ...) \
do { \
ereport(DEBUG2, (errmodule(MOD_EC), errmsg("CALL_RC ODBC: " MYODBC_STRINGIZE((FUNC)) "(" #__VA_ARGS__ ")"))); \
RC = (FUNC)(__VA_ARGS__); \
} while (false)
#define MYODBC_CALL(FUNC, ...) \
do { \
ereport(DEBUG2, (errmodule(MOD_EC), errmsg("CALL ODBC: " MYODBC_STRINGIZE((FUNC)) "(" #__VA_ARGS__ ")"))); \
(FUNC)(__VA_ARGS__); \
} while (false)
#endif
const int SQL_TINYINT_LEN = 8;
const int SQL_SMALLINT_LEN = 32;
const int SQL_BIGINT_LEN = 64;
const int INTERVAL_SECOND_LEN = 4;
namespace {
template <typename T, std::size_t N>
inline std::size_t arrlen(T (&)[N])
{
return N;
}
inline bool success(RETCODE rc)
{
#ifdef MYODBC_ODBC_API_DEBUG
std::cerr << "<-- rc: " << return _code(rc) << " | ";
#endif
return rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO;
}
#ifdef MYODBC_ODBC_API_DEBUG
inline myodbc::string_type return _code(RETCODE rc)
{
switch (rc) {
case SQL_SUCCESS:
return MYODBC_TEXT("SQL_SUCCESS");
case SQL_SUCCESS_WITH_INFO:
return MYODBC_TEXT("SQL_SUCCESS_WITH_INFO");
case SQL_ERROR:
return MYODBC_TEXT("SQL_ERROR");
case SQL_INVALID_HANDLE:
return MYODBC_TEXT("SQL_INVALID_HANDLE");
case SQL_NO_DATA:
return MYODBC_TEXT("SQL_NO_DATA");
case SQL_NEED_DATA:
return MYODBC_TEXT("SQL_NEED_DATA");
case SQL_STILL_EXECUTING:
return MYODBC_TEXT("SQL_STILL_EXECUTING");
default:
break;
}
MYODBC_ASSERT(0);
return "unknown";
}
#endif
template <typename T, std::size_t N>
inline std::size_t strarrlen(T (&a)[N])
{
const T* s = &a[0];
std::size_t i = 0;
while (*s++ && i < N) {
i++;
}
return i;
}
inline void convert(const std::string& in, std::string& out)
{
out = in;
}
* Attempts to get the most recent ODBC error as a string.
* Always return s std::string
*/
inline std::string recent_error(SQLHANDLE handle, SQLSMALLINT handle_type, long& native, std::string& state)
{
myodbc::string_type result;
std::string rvalue;
std::vector<MYODBC_SQLCHAR> sql_message(SQL_MAX_MESSAGE_LENGTH);
sql_message[0] = '\0';
SQLINTEGER i = 1;
SQLINTEGER native_error;
SQLSMALLINT total_bytes;
MYODBC_SQLCHAR sql_state[6];
RETCODE rc;
do {
MYODBC_CALL_RC(MYODBC_FUNC(*pSQLGetDiagRec),
rc,
handle_type,
handle,
(SQLSMALLINT)i,
sql_state,
&native_error,
0,
0,
&total_bytes);
if (success(rc) && total_bytes > 0) {
sql_message.resize(total_bytes + 1);
}
if (rc == SQL_NO_DATA) {
break;
}
MYODBC_CALL_RC(MYODBC_FUNC(*pSQLGetDiagRec),
rc,
handle_type,
handle,
(SQLSMALLINT)i,
sql_state,
&native_error,
sql_message.data(),
(SQLSMALLINT)sql_message.size(),
&total_bytes);
if (!success(rc)) {
convert(result, rvalue);
return rvalue;
}
if (!result.empty()) {
result += '\n';
}
result += myodbc::string_type(sql_message.begin(), sql_message.end());
i++;
} while (rc != SQL_NO_DATA);
convert(result, rvalue);
SQLCHAR* errmsg = &sql_state[0];
SQLCHAR* errmsg_endpos = &sql_state[arrlen(sql_state) - 1];
state = std::string(errmsg, errmsg_endpos);
std::string status = state;
status = status + ": ";
status = status + rvalue;
std::replace(status.begin(), status.end(), '\0', ' ');
native = native_error;
return status;
}
}
* These exceptions below will be catch in ODBC_CATCH() in odbc_connector.cpp
* so no any exception in this file can be lost.
*/
namespace myodbc {
void type_incompatible_error(int ctype, int column)
{
ereport(ERROR,
(errmodule(MOD_EC),
errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("Column %d receive unexpected C_TYPE %d", column, ctype)));
}
void null_access_error(int column)
{
ereport(ERROR,
(errmodule(MOD_EC), errcode(ERRCODE_DATA_EXCEPTION), errmsg("unexpected null string in column %d", column)));
}
void index_range_error()
{
ereport(ERROR,
(errmodule(MOD_EC),
errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("query in function exec_on_extension returns too many columns")));
}
database_error::database_error(void* handle, short handle_type, const std::string& info)
: std::runtime_error(info), native_error(0), sql_state("00000")
{
message = std::string(std::runtime_error::what()) + recent_error(handle, handle_type, native_error, sql_state);
}
database_error::~database_error(void)
{}
const char* database_error::what() const MYODBC_NOEXCEPT
{
return message.c_str();
}
const long database_error::native() const MYODBC_NOEXCEPT
{
return native_error;
}
const std::string database_error::state() const MYODBC_NOEXCEPT
{
return sql_state;
}
}
* and line numbers to be inserted into the error message. Useful for debugging.
*/
#define MYODBC_THROW_DATABASE_ERROR(handle, handle_type) \
throw myodbc::database_error(handle, handle_type, __FILE__ ":" MYODBC_STRINGIZE(__LINE__) ": ")
namespace {
using namespace std;
* Encapsulates resources needed for column binding.
* This class will be used to describe the attribute of column
*/
class bound_column {
public:
bound_column(const bound_column&) = delete;
bound_column& operator=(bound_column) = delete;
bound_column()
: name_(),
column_(0),
ctype_(0),
clen_(0),
sqltype_(0),
scale_(0),
sqlsize_(0),
blob_(false),
pdata_(0),
cbdata_(0)
{}
~bound_column()
{
delete[] pdata_;
delete[] cbdata_;
}
public:
myodbc::string_type name_;
short column_;
SQLSMALLINT ctype_;
SQLULEN clen_;
SQLSMALLINT sqltype_;
SQLSMALLINT scale_;
SQLULEN sqlsize_;
bool blob_;
char* pdata_;
myodbc::null_type* cbdata_;
};
inline void allocate_handle(SQLHENV& env, SQLHDBC& conn)
{
RETCODE rc;
MYODBC_CALL_RC(*pSQLAllocHandle, rc, SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV);
}
try {
MYODBC_CALL_RC(
*pSQLSetEnvAttr, rc, env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)MYODBC_ODBC_VERSION, SQL_IS_UINTEGER);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV);
}
MYODBC_CALL_RC(*pSQLAllocHandle, rc, SQL_HANDLE_DBC, env, &conn);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(env, SQL_HANDLE_ENV);
}
} catch (...) {
MYODBC_CALL(*pSQLFreeHandle, SQL_HANDLE_ENV, env);
throw;
}
}
}
namespace myodbc {
* Implementation of connection class: connection_impl
*/
class connection::connection_impl {
public:
connection_impl(const connection_impl&) = delete;
connection_impl& operator=(const connection_impl&) = delete;
connection_impl() : env_(0), conn_(0), connected_(false)
{
allocate_handle(env_, conn_);
}
connection_impl(const string_type& dsn, const string_type& user, const string_type& pass, long timeout)
: env_(0), conn_(0), connected_(false)
{
allocate_handle(env_, conn_);
try {
connect(dsn, user, pass, timeout);
} catch (...) {
MYODBC_CALL(*pSQLFreeHandle, SQL_HANDLE_DBC, conn_);
MYODBC_CALL(*pSQLFreeHandle, SQL_HANDLE_ENV, env_);
throw;
}
}
~connection_impl() MYODBC_NOEXCEPT
{
try {
disconnect();
} catch (...) {
}
MYODBC_CALL(*pSQLFreeHandle, SQL_HANDLE_DBC, conn_);
MYODBC_CALL(*pSQLFreeHandle, SQL_HANDLE_ENV, env_);
}
* Connection Implementation:
* connect to server with dsn, username, password and timeout
*/
void connect(const string_type& dsn, const string_type& user, const string_type& pass, long timeout,
void* event_handle = NULL)
{
disconnect();
RETCODE rc;
MYODBC_CALL_RC(*pSQLFreeHandle, rc, SQL_HANDLE_DBC, conn_);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
}
MYODBC_CALL_RC(*pSQLAllocHandle, rc, SQL_HANDLE_DBC, env_, &conn_);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(env_, SQL_HANDLE_ENV);
}
MYODBC_CALL_RC(*pSQLSetConnectAttr, rc, conn_, SQL_LOGIN_TIMEOUT, (SQLPOINTER)(std::intptr_t)timeout, 0);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
}
MYODBC_CALL_RC(MYODBC_FUNC(*pSQLConnect),
rc,
conn_,
(MYODBC_SQLCHAR*)dsn.c_str(),
SQL_NTS,
!user.empty() ? (MYODBC_SQLCHAR*)user.c_str() : 0,
SQL_NTS,
!pass.empty() ? (MYODBC_SQLCHAR*)pass.c_str() : 0,
SQL_NTS);
if (!success(rc) && (event_handle == NULL || rc != SQL_STILL_EXECUTING)) {
MYODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
}
connected_ = success(rc);
}
bool connected() const
{
return connected_;
}
void disconnect()
{
if (connected()) {
RETCODE rc;
MYODBC_CALL_RC(*pSQLDisconnect, rc, conn_);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(conn_, SQL_HANDLE_DBC);
}
}
connected_ = false;
}
void* native_dbc_handle() const
{
return conn_;
}
void* native_env_handle() const
{
return env_;
}
public:
HENV env_;
HDBC conn_;
private:
bool connected_;
};
}
namespace myodbc {
* Implementation of statement class: statement_impl
*/
class statement::statement_impl {
public:
statement_impl(const statement_impl&) = delete;
statement_impl& operator=(const statement_impl&) = delete;
statement_impl() : stmt_(0), remote_ncols_(-1), open_(false), conn_()
{}
~statement_impl() MYODBC_NOEXCEPT
{
if (open() && connected()) {
MYODBC_CALL(*pSQLCancel, stmt_);
MYODBC_CALL(*pSQLFreeHandle, SQL_HANDLE_STMT, stmt_);
}
}
* Close old connection and reopen one new connection
*/
void open(class connection& conn)
{
close();
RETCODE rc;
MYODBC_CALL_RC(*pSQLAllocHandle, rc, SQL_HANDLE_STMT, conn.native_dbc_handle(), &stmt_);
open_ = success(rc);
if (!open_) {
MYODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
}
conn_ = conn;
}
bool open() const
{
return open_;
}
bool connected() const
{
return conn_.connected();
}
void close()
{
if (open() && connected()) {
RETCODE rc;
MYODBC_CALL_RC(*pSQLCancel, rc, stmt_);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
}
MYODBC_CALL_RC(*pSQLFreeHandle, rc, SQL_HANDLE_STMT, stmt_);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
}
}
remote_ncols_ = -1;
open_ = false;
stmt_ = 0;
}
* Entrance of the execution
*/
result execute_direct(
class connection& conn, const string_type& query, long batch_operations, long timeout, statement& statement)
{
just_execute_direct(conn, query, batch_operations, timeout, statement);
return result(statement, batch_operations);
}
* Actual execution function
*/
RETCODE just_execute_direct(class connection& conn, const string_type& query, long batch_operations, long timeout,
statement&,
void* event_handle = NULL)
{
open(conn);
RETCODE rc;
MYODBC_CALL_RC(MYODBC_FUNC(*pSQLExecDirect), rc, stmt_, (MYODBC_SQLCHAR*)query.c_str(), SQL_NTS);
if (!success(rc) && rc != SQL_NO_DATA && rc != SQL_STILL_EXECUTING) {
MYODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
}
return rc;
}
short columns()
{
if (remote_ncols_ == -1) {
RETCODE rc;
MYODBC_CALL_RC(*pSQLNumResultCols, rc, stmt_, &remote_ncols_);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(stmt_, SQL_HANDLE_STMT);
}
}
return remote_ncols_;
}
void* native_statement_handle() const
{
return stmt_;
}
public:
HSTMT stmt_;
private:
SQLSMALLINT remote_ncols_;
bool open_;
class connection conn_;
};
}
namespace myodbc {
* Implementation of result class: result_impl
*/
class result::result_impl {
public:
result_impl(const result_impl& impl) = delete;
result_impl& operator=(const result_impl& impl) = delete;
result_impl(statement stmtobj, long rowset_len)
: stmt_(stmtobj),
rowset_length(rowset_len),
row_number(1),
bound_columns_(0),
bound_columns_length(0),
rowset_pos(0),
bound_columns_by_name(),
at_end(false)
{
* Bind the column attribute with results from the server
*/
SQLLEN bufferSize = sizeof(blob_buffer);
errno_t errorno = memset_s(blob_buffer, bufferSize, 0, bufferSize);
securec_check(errorno, "\0", "\0");
auto_bind();
}
~result_impl() MYODBC_NOEXCEPT
{
cleanup_bound_columns();
}
void* native_statement_handle() const
{
return stmt_.native_statement_handle();
}
long rowset_size() const
{
return rowset_length;
}
long rows() const MYODBC_NOEXCEPT
{
return static_cast<long>(row_number);
}
short columns()
{
return stmt_.columns();
}
bool next()
{
if (rows() && ++rowset_pos < rowset_length) {
return rowset_pos < rows();
}
rowset_pos = 0;
return fetch(0, SQL_FETCH_NEXT);
}
bool is_null(short column) const
{
if (column >= bound_columns_length) {
index_range_error();
}
bound_column& col = bound_columns_[column];
if (rowset_pos >= rows()) {
index_range_error();
}
return col.cbdata_[rowset_pos] == SQL_NULL_DATA;
}
int column_c_datatype(short column) const
{
if (column >= bound_columns_length) {
index_range_error();
}
bound_column& col = bound_columns_[column];
return col.ctype_;
}
template <class T>
void get_ref(short column, T& result)
{
if (column >= bound_columns_length) {
index_range_error();
}
if (is_null(column)) {
null_access_error(column);
}
get_ref_impl<T>(column, result);
}
template <class T>
T get(short column)
{
T result;
get_ref(column, result);
return result;
}
private:
void get_blob_data(short column, string_type& result);
template <class T>
void get_ref_impl(short column, T& result);
void before_move() MYODBC_NOEXCEPT
{
for (short i = 0; i < bound_columns_length; i++) {
bound_column& colobj = bound_columns_[i];
for (long j = 0; j < rowset_length; j++)
colobj.cbdata_[j] = 0;
if (colobj.blob_ && colobj.pdata_) {
release_bound_resources(i);
}
}
}
void release_bound_resources(short column) MYODBC_NOEXCEPT
{
MYODBC_ASSERT(column < bound_columns_length);
bound_column& col = bound_columns_[column];
delete[] col.pdata_;
col.pdata_ = 0;
col.clen_ = 0;
}
void cleanup_bound_columns() MYODBC_NOEXCEPT
{
before_move();
delete[] bound_columns_;
bound_columns_ = NULL;
bound_columns_length = 0;
bound_columns_by_name.clear();
}
bool fetch(long rows, SQLUSMALLINT orientation)
{
before_move();
RETCODE rc;
MYODBC_CALL_RC(*pSQLFetch, rc, stmt_.native_statement_handle());
if (rc == SQL_NO_DATA) {
at_end = true;
return false;
}
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
}
return true;
}
* find the SQL data type of the column in result set, and provide the according
* SQL C data type.
* the others column attrs are alse set here, such as null flag, column name,
* the number of the column sequence ...
*/
void auto_bind()
{
cleanup_bound_columns();
const short n_columns = columns();
if (n_columns < 1) {
return;
}
MYODBC_ASSERT(!bound_columns_);
MYODBC_ASSERT(!bound_columns_length);
bound_columns_ = new bound_column[n_columns];
bound_columns_length = n_columns;
RETCODE rc;
MYODBC_SQLCHAR column_name[1024];
SQLSMALLINT sqltype, scale, nullable, len;
SQLULEN sqlsize;
for (SQLSMALLINT i = 0; i < n_columns; i++) {
MYODBC_CALL_RC(MYODBC_FUNC(*pSQLDescribeCol),
rc,
stmt_.native_statement_handle(),
i + 1,
(MYODBC_SQLCHAR*)column_name,
sizeof(column_name) / sizeof(MYODBC_SQLCHAR),
&len,
&sqltype,
&sqlsize,
&scale,
&nullable);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
}
bool is_blob = false;
if (sqlsize == 0) {
switch (sqltype) {
case SQL_WVARCHAR: {
* Divide in half, due to sqlsize being 32-bit in Win32 (and 64-bit in x64)
* sqlsize = std::numeric_limits<int32_t>::max() / 2 - 1;
*/
is_blob = true;
}
default:
break;
}
}
bound_column& colobj = bound_columns_[i];
colobj.name_ = reinterpret_cast<string_type::value_type *>(column_name);
colobj.column_ = i;
colobj.sqltype_ = sqltype;
colobj.sqlsize_ = sqlsize;
colobj.scale_ = scale;
bound_columns_by_name[colobj.name_] = &colobj;
colobj.blob_ = false;
using namespace std;
switch (colobj.sqltype_) {
case SQL_TINYINT: {
colobj.ctype_ = SQL_C_CHAR;
colobj.clen_ = SQL_TINYINT_LEN;
break;
}
case SQL_SMALLINT: {
colobj.ctype_ = SQL_C_CHAR;
colobj.clen_ = SQL_SMALLINT_LEN;
break;
}
case SQL_INTEGER:
case SQL_BIGINT:
case SQL_DOUBLE:
case SQL_FLOAT:
case SQL_REAL: {
colobj.ctype_ = SQL_C_CHAR;
colobj.clen_ = SQL_BIGINT_LEN;
break;
}
case SQL_DECIMAL:
case SQL_NUMERIC: {
colobj.ctype_ = SQL_C_CHAR;
colobj.clen_ = colobj.sqlsize_ + 1;
break;
}
case SQL_DATE:
case SQL_TYPE_DATE: {
colobj.ctype_ = SQL_C_DATE;
colobj.clen_ = sizeof(date);
break;
}
case SQL_TIMESTAMP:
case SQL_TYPE_TIMESTAMP: {
colobj.ctype_ = SQL_C_TIMESTAMP;
colobj.clen_ = sizeof(timestamp);
break;
}
case SQL_CHAR:
case SQL_VARCHAR: {
colobj.ctype_ = SQL_C_CHAR;
* For type: interval second(p): saved as 00:00:12.999999 in MPPDB.
* unixODBC give shorter estimate sqlsize=12, so we can only get 00:00:12.999 (sth be lost)
* For this reasion, we enlarge the sqlsize here(will be used to bind column below) as:
* col.sqlsize_ + 1 (for '\0') + 3 (enlarged)
*/
colobj.clen_ = (colobj.sqlsize_ + INTERVAL_SECOND_LEN) * sizeof(SQLCHAR);
break;
}
case SQL_WCHAR:
case SQL_WVARCHAR: {
colobj.ctype_ = SQL_C_WCHAR;
colobj.clen_ = (colobj.sqlsize_ + 1) * sizeof(SQLWCHAR);
if (is_blob) {
colobj.clen_ = 0;
colobj.blob_ = true;
}
break;
}
case SQL_LONGVARCHAR: {
colobj.ctype_ = SQL_C_CHAR;
colobj.blob_ = true;
colobj.clen_ = 0;
break;
}
default: {
throw std::runtime_error("SQL_ERROR: unsupport data type return by ODBC");
}
}
}
for (SQLSMALLINT i = 0; i < n_columns; ++i) {
bound_column& colobj = bound_columns_[i];
colobj.cbdata_ = new null_type[rowset_length];
if (colobj.blob_) {
MYODBC_CALL_RC(
*pSQLBindCol, rc, stmt_.native_statement_handle(), i + 1, colobj.ctype_, 0, 0, colobj.cbdata_);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
}
} else {
colobj.pdata_ = new char[rowset_length * colobj.clen_];
MYODBC_CALL_RC(*pSQLBindCol,
rc,
stmt_.native_statement_handle(),
i + 1,
colobj.ctype_,
colobj.pdata_,
colobj.clen_,
colobj.cbdata_);
if (!success(rc)) {
MYODBC_THROW_DATABASE_ERROR(stmt_.native_statement_handle(), SQL_HANDLE_STMT);
}
}
}
}
private:
statement stmt_;
const long rowset_length;
SQLULEN row_number;
bound_column* bound_columns_;
short bound_columns_length;
long rowset_pos;
std::map<string_type, bound_column *> bound_columns_by_name;
bool at_end;
char blob_buffer[1024];
};
* @Description:
* get blob data from server
*
* @IN column: column number
* @IN result: where blob data stored
* @RETURN: void
*/
void result::result_impl::get_blob_data(short column, string_type& result)
{
bound_column& col = bound_columns_[column];
std::string out;
SQLLEN strlen_or_ind;
SQLRETURN rc;
void* handle = native_statement_handle();
SQLLEN bufferSize = sizeof(blob_buffer);
SQLLEN ntotal = 0;
SQLUSMALLINT icol = column + 1;
MYODBC_CALL_RC(*pSQLGetData, rc, handle, icol, col.ctype_, blob_buffer, 0, &ntotal);
if (rc == SQL_SUCCESS_WITH_INFO) {
while (ntotal > 0) {
errno_t errorno = memset_s(blob_buffer, bufferSize, 0, bufferSize);
securec_check(errorno, "\0", "\0");
MYODBC_CALL_RC(*pSQLGetData, rc, handle, icol, col.ctype_, blob_buffer, bufferSize, &strlen_or_ind);
* SQLGetData returns
*
* SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NO_DATA: --success
* SQL_STILL_EXECUTING,
* SQL_ERROR, SQL_INVALID_HANDLE: --fail
*/
if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) {
if (ntotal > (bufferSize - 1)) {
out.append(blob_buffer, bufferSize - 1);
} else {
out.append(blob_buffer, ntotal);
}
ntotal = ntotal - (bufferSize - 1);
} else if (rc == SQL_NO_DATA) {
break;
} else {
throw std::runtime_error("SQL_ERROR: unexpected error in getting blob data from server!");
}
}
}
convert(out, result);
}
* @Description:
* get data as a string from the result buffer
*
* @IN column: column number
* @IN result: where data stored
* @RETURN: void
*/
template <>
inline void result::result_impl::get_ref_impl<string_type>(short column, string_type& result)
{
bound_column& col = bound_columns_[column];
* Get string data by C_TYPE
*/
switch (col.ctype_) {
case SQL_C_CHAR: {
if (col.blob_) {
get_blob_data(column, result);
} else {
const char* s = col.pdata_ + rowset_pos * col.clen_;
const std::string::size_type str_size = std::strlen(s);
result.assign(s, s + str_size);
}
break;
}
case SQL_C_DATE: {
char date_str[64] = {0};
errno_t ret;
date d = *reinterpret_cast<date *>(col.pdata_ + rowset_pos * col.clen_);
ret = sprintf_s(date_str, sizeof(date_str), "%d-%d-%d", d.year, d.month, d.day);
securec_check_ss(ret, "\0", "\0");
convert(date_str, result);
break;
}
case SQL_C_TIMESTAMP: {
char date_str[64] = {0};
errno_t ret;
timestamp stamp = *reinterpret_cast<timestamp *>(col.pdata_ + rowset_pos * col.clen_);
ret = sprintf_s(date_str,
sizeof(date_str),
"%d-%d-%d %d:%d:%d.%09d",
stamp.year,
stamp.month,
stamp.day,
stamp.hour,
stamp.min,
stamp.sec,
stamp.fract);
securec_check_ss(ret, "\0", "\0");
convert(date_str, result);
break;
}
default:
type_incompatible_error(col.ctype_, col.column_);
}
}
* @Description:
* get data as a TEMPLATE_TYPE from the result buffer
*
* @IN column: column number
* @IN result: where data stored
* @RETURN: void
*/
template <>
inline void result::result_impl::get_ref_impl<bool>(short column, bool& result)
{
bound_column& col = bound_columns_[column];
using namespace std;
const char* s = NULL;
* Check if convert blob type(e.g. text) into bool, if so just error
*/
if (col.blob_ || col.pdata_ == NULL) {
type_incompatible_error(col.ctype_, col.column_);
} else {
s = col.pdata_ + rowset_pos * col.clen_;
}
if (s == NULL) {
ereport(ERROR,
(errmodule(MOD_EC),
errcode(ERRCODE_DATA_EXCEPTION),
errmsg("SQL_ERROR: unexpected data while converting it into bool.")));
}
* Convert buffer data according to C_TYPE
*/
switch (col.ctype_) {
case SQL_C_CHAR:
if (*s == '0' || *s == 'f' || *s == 'F' || *s == 'n' || *s == 'N' || strcasecmp(s, "no") == 0 ||
strcasecmp(s, "false") == 0) {
result = false;
} else if (*s == '1' || *s == 't' || *s == 'T' || *s == 'y' || *s == 'Y' || strcasecmp(s, "yes") == 0 ||
strcasecmp(s, "true") == 0) {
result = true;
} else {
throw std::runtime_error("SQL_ERROR: unexpected data while converting it into bool.");
}
break;
default:
type_incompatible_error(col.ctype_, col.column_);
}
}
}
namespace myodbc {
* Entrance of Execution
*/
result execute(connection& conn, const string_type& query, long batch_operations, long timeout)
{
class statement statement;
return statement.execute_direct(conn, query, batch_operations, timeout);
}
}
namespace myodbc {
* Interface function of Connection Class: connection
*/
connection::connection() : impl_(new connection_impl())
{}
connection& connection::operator=(connection rhs)
{
swap(rhs);
return *this;
}
void connection::swap(connection& rhs_) MYODBC_NOEXCEPT
{
using std::swap;
swap(impl_, rhs_.impl_);
}
connection::connection(const string_type& dsn, const string_type& user, const string_type& pass, long timeout)
: impl_(new connection_impl(dsn, user, pass, timeout))
{}
connection::~connection() MYODBC_NOEXCEPT
{}
void connection::connect(const string_type& dsn, const string_type& user, const string_type& pass, long timeout)
{
impl_->connect(dsn, user, pass, timeout);
}
bool connection::connected() const
{
return impl_->connected();
}
void connection::disconnect()
{
impl_->disconnect();
}
void* connection::native_dbc_handle() const
{
return impl_->native_dbc_handle();
}
void* connection::native_env_handle() const
{
return impl_->native_env_handle();
}
}
namespace myodbc {
* Interface function of Statement Class: statement
*/
statement::statement() : impl_(new statement_impl())
{}
statement::statement(statement&& rhs) MYODBC_NOEXCEPT : impl_(std::move(rhs.impl_))
{}
statement::statement(const statement& rhs) : impl_(rhs.impl_)
{}
statement& statement::operator=(statement rhs)
{
swap(rhs);
return *this;
}
void statement::swap(statement& rhs) MYODBC_NOEXCEPT
{
using std::swap;
swap(impl_, rhs.impl_);
}
statement::~statement() MYODBC_NOEXCEPT
{}
void statement::open(class connection& conn)
{
impl_->open(conn);
}
bool statement::open() const
{
return impl_->open();
}
bool statement::connected() const
{
return impl_->connected();
}
void statement::close()
{
impl_->close();
}
void* statement::native_statement_handle() const
{
return impl_->native_statement_handle();
}
result statement::execute_direct(class connection& conn, const string_type& query, long batch_operations, long timeout)
{
return impl_->execute_direct(conn, query, batch_operations, timeout, *this);
}
short statement::columns()
{
return impl_->columns();
}
}
namespace myodbc {
* Interface function of Result Class: result
*/
result::result() : impl_()
{}
result::~result() MYODBC_NOEXCEPT
{}
result::result(statement stmt, long rowset_size) : impl_(new result_impl(stmt, rowset_size))
{}
result::result(result&& rhs) MYODBC_NOEXCEPT : impl_(std::move(rhs.impl_))
{}
result::result(const result& rhs) : impl_(rhs.impl_)
{}
result& result::operator=(result rhs)
{
swap(rhs);
return *this;
}
void result::swap(result& rhs) MYODBC_NOEXCEPT
{
using std::swap;
swap(impl_, rhs.impl_);
}
short result::columns()
{
return impl_->columns();
}
bool result::next()
{
return impl_->next();
}
bool result::is_null(short column) const
{
return impl_->is_null(column);
}
int result::column_c_datatype(short column) const
{
return impl_->column_c_datatype(column);
}
template <class T>
T result::get(short column)
{
return impl_->get<T>(column);
}
}
#undef MYODBC_THROW_DATABASE_ERROR
#undef MYODBC_STRINGIZE
#undef MYODBC_STRINGIZE_I
#undef MYODBC_CALL_RC
#undef MYODBC_CALL
* @Description: connect to the server by DSN
*
* @IN value: conn_str, the DSN sent to the unixODBC.
* @IN return: connection handle
*/
CONN_HANDLE odbc_lib_connect(const char* dsn, const char* user, const char* pass)
{
myodbc::string_type dsn_str = (dsn == NULL ? "" : dsn);
myodbc::string_type user_str = (user == NULL ? "" : user);
myodbc::string_type pass_str = (pass == NULL ? "" : pass);
return new myodbc::connection(dsn_str, user_str, pass_str);
}
* @Description: clean up the resources such as connection and result objects.
*
* @IN value: conn, the handle of the connection object.
* @IN value: result, the handle of the result object.
*/
void odbc_lib_cleanup(CONN_HANDLE conn, RESULT_HANDLE result)
{
myodbc::result* _result = (myodbc::result*)result;
if (NULL != _result) {
delete _result;
}
myodbc::connection* _conn = (myodbc::connection*)conn;
if (_conn != NULL) {
if (_conn->connected()) {
_conn->disconnect();
}
delete _conn;
}
}
* @Description: send SQL to the remote service.
*
* @IN value: conn, the handle of the connection object.
* @IN value: query, the SQL sent to the server
*/
RESULT_HANDLE odbc_lib_query(CONN_HANDLE conn, const char* query)
{
myodbc::connection* _conn = (myodbc::connection*)conn;
myodbc::string_type query_str = (query == NULL ? "" : query);
myodbc::result* _result = new myodbc::result();
*_result = execute(*_conn, query_str);
return _result;
}
* @Description: get one row of the result set from odbc driver.
*
* @IN value: result, the handle of the result object.
* @IN value: types, the type oid array of the column
* @IN value: _buf, the buffer array of the result
* @IN value: nulls, the flags array of the null
* @IN value: cols, the number of the target column
* @IN value: isEnd, true if no more data returned.
*/
void odbc_lib_get_result(RESULT_HANDLE result, unsigned int* types, void** _buf, bool* nulls, int cols, bool& isend)
{
myodbc::result* _result = (myodbc::result*)result;
int remote_ncols = (int)_result->columns();
if (0 == remote_ncols) {
isend = true;
return;
}
if (cols > remote_ncols) {
throw std::runtime_error("SQL_ERROR: The number of the column in relation"
" definition is greater than the number of the column in result!");
}
isend = !_result->next();
if (isend) {
return;
}
#define GetStringToBuffer \
do { \
errno_t ret; \
std::string tmp = _result->get<string>(col); \
buf[col] = (int64_t)palloc0(tmp.size() + 1); \
ret = memcpy_s((void*)buf[col], tmp.size() + 1, tmp.c_str(), tmp.size() + 1); \
securec_check(ret, "\0", "\0"); \
} while (0)
int64_t* buf = (int64_t*)_buf;
for (int col = 0; col < cols; ++col) {
if (_result->is_null(col)) {
nulls[col] = true;
continue;
}
int sql_c_type = _result->column_c_datatype(col);
switch (types[col]) {
* Boolean: read bool and then store as one byte.
*/
case BOOLOID: {
if (sql_c_type == SQL_C_CHAR) {
bool x = false;
x = _result->get<bool>(col);
buf[col] = (int64_t)palloc0(2);
*(char*)buf[col] = (x == true) ? '1' : '0';
*((char*)buf[col] + 1) = '\0';
} else
throw std::runtime_error("SQL_ERROR: cast unexpected type to bool.");
break;
}
* Numeric: up to now, number is stored as a string
*/
case INT1OID:
if (sql_c_type == SQL_C_CHAR) {
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to tinyint.");
}
break;
case INT2OID:
if (sql_c_type == SQL_C_CHAR) {
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to smallint.");
}
break;
case INT4OID:
if (sql_c_type == SQL_C_CHAR) {
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to int.");
}
break;
case INT8OID:
if (sql_c_type == SQL_C_CHAR) {
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to bigint.");
}
break;
case FLOAT4OID:
if (sql_c_type == SQL_C_CHAR) {
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to float.");
}
break;
case FLOAT8OID:
if (sql_c_type == SQL_C_CHAR) {
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to double.");
}
break;
case NUMERICOID:
if (sql_c_type == SQL_C_CHAR) {
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to numeric.");
}
break;
* Characters/String
*/
case CHAROID:
case BPCHAROID:
if (sql_c_type == SQL_C_CHAR) {
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to char.");
}
break;
case VARCHAROID:
if (sql_c_type == SQL_C_CHAR) {
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to varchar.");
}
break;
case NVARCHAR2OID:
if (sql_c_type == SQL_C_CHAR) {
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to nvarchar2.");
}
break;
* Date/Timestamp
*/
case TIMESTAMPOID:
case DATEOID:
case TIMESTAMPTZOID: {
if (sql_c_type == SQL_C_TIMESTAMP || sql_c_type == SQL_C_DATE) {
* Store the date or timestamp as a string
*/
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to date/timestamp.");
}
break;
}
* Interval
*/
case INTERVALOID: {
if (sql_c_type == SQL_C_CHAR) {
* Store the interval as a string
*/
GetStringToBuffer;
} else {
throw std::runtime_error("SQL_ERROR: cast unexpected type to interval.");
}
break;
}
* Text
*/
case TEXTOID: {
* Store the result as a string no matter what sql_c_type is
*/
GetStringToBuffer;
break;
}
* Default: should never run here, check_typeoid() has checked.
*/
default:
throw std::runtime_error("SQL_ERROR: unsupport data type found in table definition.");
}
}
}