/*
 *  KZ ODBC
 *  Copyright (C) 2010 Katsuhisa Ohfuji <katsuhisa@ohfuji.name>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */
#ifndef KZ_ODBC_H
#define KZ_ODBC_H
/**********************************************************************
 kz_odbc.h  ODBC APIの簡易ラッパークラス
 Authors	Katsuhisa Ohfuji
 Version	0.1a(開発版）2008/03/11
 Version	0.2a(開発版）2008/04/14 NULL値の取得エラーの修正
									（NULL値を空文字として取得する）
 Version	0.3a(開発版）2008/05/16 カタログ関数(tables,columns)の追加
 Version	0.4a(開発版）2009/01/06 Linux対応（＋一部関数の追加）
 Version	0.5a(開発版）2009/02/12 colinfoの添え字調整
 Version	0.6a(開発版）2009/02/27 トランザクション対応
									Fetch関数（next）の追加
 Version	0.7a(開発版）2009/04/10 データ取得のバグ修正
 Version	0.7b(開発版）2009/05/07 引数のデータ型の訂正
 Version	0.8a(開発版）2009/05/12 NULLデーター挿入の対応
 Version	0.8b(開発版）2009/12/11 64bit修正
 Version    0.9          2011/04/14 バグフィックス
 Version    0.91         2011/08/30 kz_odbc::get_constrを追加
 Version    0.92         2012/04/14 bindメッソッド等をパブリックへ
									BINARYデータのフェッチのバグ対応
 Version	0.93		 2012/05/10 ハンドルの確保・解放をODBC3.0準拠へ
 
 使い方（Ver0.6から使い方が変わりました）
    1.kz_odbcオブジェクトを作成（コンストラクタにODBC接続文字列を指定）

     kz_odbc db("DSN=myaccess", false);
   
     ※DSNでデーターソースの指定のほか、Driverでの指定も可
     ※コンストラクタの2つ目の引数はコミットモードを指定する
	   trueで手動コミットモード、falseで自動コミットモード
	   手動コミットモードにした場合、最後にCommit()を呼び出す必要がある。
	   
    2.SQLの発行
	 stmtオブジェクトを作成し、SQLを発行する。
	 
     kz_stmt stmt(&db);

     stmt, "INSERT INTO TEST(col1, col2) VALUE(?, ?)", "val1", 10, endsql;

     ※各要素をカンマ(,)で区切る
     ※最初にkz_stmtオブジェクトを指定し、次にSQLを文字列で指定する。
     ※パラメーター（？に対する値）を続けて指定
     ※パラメーターで指定できる型は、整数(各int)、文字（char*)、
       浮動小数点（double,float）、時間（struct tm)
       それ以外の型を指定するとSQLが発行されない（式全体が無視される）。
     ※整数の0をパラメータに指定する時は一度変数に入れてから指定する。
     　（C++コンパイラでは、整数の0は、NULLポインタと解釈されるので）
	 ※NULL値を指定したいときは、kz_odbc_nullを指定する。
		stmt, "INSERT INTO TEST(col1) VALUE(?)", kz_odbc_null, endsql;
     ※最後にendsqlと記述する（記述がないとSQLが発行されない）。
     
    3.値を返すSQLの発行
    
     kz_resultset_array result = (stmt, "SELECT * FROM TEST", endsqlrs);
    
     ※ 結果セットは、kz_resultset_array（後述）で返る
     ※ SQL発行部のカンマ式の全体を()で括る。
        演算子の優先順位の関係で全体を括らないと値が返らない。
     ※ 最後は、endsqlrsと記述する（endsqlでは値が返らない）。
    
    4.結果セットの参照方法
     結果セット（kz_resultset_array）は、以下のtypdefになっている。
     	std::vector< std::map< std::string, std::string> >
     ※SELECTの結果を全て受け取る
     ※行はvectorに列はmap（列名と値のmap)になっている。
     　　cout << result[1]["ID"]
     　のようにアクセスできる。
     ※結果セットは、文字列に変換されて返る
    5.エラーの確認
	 ※iserrメンバ関数でエラーの有無が分かる
	 ※errorsメンバ関数でエラー情報が取得できる
	 　結果は、kz_string_array( std::vector< std::string > )で返る。
 サンプル
　kz_odbc_sample.cppをご参照下さい。
 ライセンス
 　本ソフトウェアはGPLで配布しています。
**********************************************************************/
#include <string>
#include <strstream>
#include <vector>
#include <map>
#include <time.h>

#ifdef _WIN32
#include <windows.h>
#endif
#include <sql.h>
#include <sqlext.h>

class	kz_odbc_marker_end_sql{
	void *dmy;
public:
	kz_odbc_marker_end_sql() : dmy(0) {}	
};
inline kz_odbc_marker_end_sql get_kz_odbc_marker_end_sql() { return kz_odbc_marker_end_sql(); }
#define endsql get_kz_odbc_marker_end_sql()

class	kz_odbc_marker_end_sql_resultsets{
	void *dmy;
public:
	kz_odbc_marker_end_sql_resultsets() : dmy(0) {}
};
inline kz_odbc_marker_end_sql_resultsets get_kz_odbc_marker_end_sql_resultsets() { return kz_odbc_marker_end_sql_resultsets(); }
#define endsqlrs get_kz_odbc_marker_end_sql_resultsets()

class	kz_odbc_maker_null {
	void *dmy;
public:
	kz_odbc_maker_null() : dmy(0) {}
};
inline kz_odbc_maker_null get_kz_odbc_maker_null() { return kz_odbc_maker_null(); }
#define kz_odbc_null  get_kz_odbc_maker_null()

#define KZSQLSUCCESS(rc) ((rc==SQL_SUCCESS)||(rc==SQL_SUCCESS_WITH_INFO)||(rc==SQL_NO_DATA))
#define MAX_RESULT_COLUMN_NAME_LEN	128

typedef std::map< std::string, std::string>		kz_resultset;
typedef std::pair< std::string, std::string>	kz_resultset_pair;
typedef std::vector< kz_resultset >	kz_resultset_array;
typedef std::vector< std::string > kz_string_array;

class	kz_odbc {
friend class kz_stmt;
private:
	RETCODE		rc;
	HENV		henv;
	HDBC		hdbc;
	const char*	constr;
	bool		tranflg;
	
	void connect() {
		SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
		SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC2, -1);
		SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
		rc = SQLDriverConnect(hdbc, 0, (SQLCHAR*)constr, SQL_NTS, 0, 0, 0, SQL_DRIVER_NOPROMPT);
		if ( !iserr() && tranflg ) {
			BeginTran();
		}
	}

	void dissconnect() {
		if ( tranflg ) { Rollback(); }
		if ( hdbc ) { SQLDisconnect(hdbc); }
		if ( hdbc ) { SQLFreeHandle(SQL_HANDLE_DBC,hdbc); hdbc = 0; }
		if ( henv ) { SQLFreeHandle(SQL_HANDLE_ENV,henv); henv = 0; }
	}
public:	
	bool iserr() { return !KZSQLSUCCESS(rc); }
public:
	void errclear() { rc = SQL_SUCCESS; }

public:
	kz_odbc() : rc(SQL_SUCCESS), henv(0), hdbc(0), constr(0), tranflg(false) {}
	kz_odbc( const kz_odbc &src) : rc(SQL_SUCCESS), henv(0), hdbc(0), constr(src.constr), tranflg(src.tranflg) {}
	kz_odbc( const char *l_constr, bool tranflg_) : rc(SQL_SUCCESS), henv(0), hdbc(0), constr(l_constr), tranflg(tranflg_) {}
	kz_odbc( const std::string &l_constr, bool tranflg_) : rc(SQL_SUCCESS), henv(0), constr(l_constr.c_str()), tranflg(tranflg_) {}
	const char *get_constr() { return constr; }
	void set_constr(const char *l_constr) { constr = l_constr; }
	~kz_odbc() { dissconnect(); }
	
	void BeginTran() {
		rc = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF,0 );
	}

	void Commit() {
		rc = SQLEndTran( SQL_HANDLE_DBC, hdbc, SQL_COMMIT);
	}

	void Rollback() {
		rc = SQLEndTran( SQL_HANDLE_DBC, hdbc, SQL_ROLLBACK);
	}

	kz_string_array errors() {
		kz_string_array	result;
		char			state[6];
		SQLINTEGER		nerr;
		char			msg[SQL_MAX_MESSAGE_LENGTH+1];
		SQLSMALLINT		msglen;
		msg[SQL_MAX_MESSAGE_LENGTH] = '\0';
		
		while ( SQLError( henv, hdbc, 0, (SQLCHAR*)state, &nerr, (SQLCHAR*)msg, sizeof(msg), &msglen) == SQL_SUCCESS ) {
			std::strstream err;
			err << state << "(" << nerr << "):" << msg;
			result.push_back( std::string(std::istreambuf_iterator<char>(err), std::istreambuf_iterator<char>()));
		}
		return result;	
	}
};

struct describe_col {
	std::string		name;
	SQLSMALLINT		typ;
	SQLULEN			siz;
	SQLSMALLINT		dd;
	describe_col( char *name_, SQLSMALLINT typ_, SQLULEN siz_, SQLSMALLINT dd_) : name(name_), typ(typ_), siz(siz_), dd(dd_) {}
};

class	kz_stmt {
	kz_odbc		*con;
	RETCODE		rc;
	HSTMT		hstmt;
	const char*	sql;
	int			count;
	std::vector<char>	buff;
	std::vector<TIMESTAMP_STRUCT>	tsary;
	std::vector< describe_col > colinfo;

public:	
	bool iserr() { return !KZSQLSUCCESS(rc); }
	void errclear() { rc = SQL_SUCCESS; count = 0; }

	void freestmt() {
		if ( hstmt ) { 
			SQLCloseCursor(hstmt);
			SQLFreeHandle( SQL_HANDLE_STMT, hstmt);
			hstmt = 0; 
			count = 0; 
		}
	}
	void allocstmt() {
		errclear();
		tsary.clear();
		freestmt();
		rc = SQLAllocHandle(SQL_HANDLE_STMT, con->hdbc, &hstmt);
		//rc = SQLAllocStmt(con->hdbc,&hstmt);
	}

	void tables_(void) {
		if ( !con ) return;
		if ( !con->hdbc ) { con->connect(); }
		allocstmt();
		colinfo.clear();
		rc = SQLTables( hstmt, 0, 0, 0, 0, 0, 0, (SQLCHAR*)" 'TABLE', 'VIEW', 'ALIAS', 'SYNONYM' ", SQL_NTS);
	}
	
	void columns_(const char*table) {
		if ( !con ) return;
		if ( !con->hdbc ) { con->connect(); }
		allocstmt();
		colinfo.clear();
		rc = SQLColumns(hstmt, 0, 0, 0, 0, (SQLCHAR*)table, SQL_NTS, 0, 0);
	}
	void execdirect(const char *sql) {
		if ( !con ) return;
		if ( !con->hdbc ) { con->connect(); }
		allocstmt();
		colinfo.clear();
		this->sql = sql;
		rc = SQLExecDirect(hstmt,(SQLCHAR*)sql,SQL_NTS);
	}
	void prepare(const char *sql) {
		if ( count != 0 ) return;
		if ( !con ) return;
		if ( !con->hdbc ) { con->connect(); }
		if ( iserr() ) return;
		allocstmt();
		if ( iserr() ) return;
		colinfo.clear();
		this->sql = sql;
		rc = SQLPrepare(hstmt, (SQLCHAR*)sql, SQL_NTS);
	}
	void bind( int ValueType, int ParameterType, const void *param, SQLLEN ColumnSize = 0, int DecimalDigits = 0, SQLLEN *written = 0) {
		if ( count == 0 ) { count = 1; return; }
		if ( iserr() ) return;
		rc = SQLBindParameter(hstmt, count++, SQL_PARAM_INPUT, ValueType, ParameterType, ColumnSize, DecimalDigits,  (SQLPOINTER)param, 0, written);
	}
	void bind_null() {
		static SQLLEN	sqlnull = SQL_NULL_DATA;
		if ( count == 0 ) { count = 1; return; }
		if ( iserr() ) return;
		rc = SQLBindParameter(hstmt, count++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 0, 0,  0, 0, &sqlnull);
	}

	void execute() {
		if ( iserr() ) return;
		rc = SQLExecute(hstmt);
		count = 0;
	}
	void get_colinfo() {
		if ( !colinfo.empty() ) return;
		if ( iserr() ) return;
		SQLSMALLINT colnum;
		SQLNumResultCols( hstmt, &colnum);
		for ( int i = 1; i <= colnum; i++ ) {
			char cname[MAX_RESULT_COLUMN_NAME_LEN+1];
			SQLSMALLINT		cnamelength;
			SQLSMALLINT		ctype;
			SQLULEN			csize;
			SQLSMALLINT		cdd;
			SQLDescribeCol( hstmt, i, (SQLCHAR*)cname, MAX_RESULT_COLUMN_NAME_LEN, &cnamelength, &ctype, &csize, &cdd, 0);
			cname[MAX_RESULT_COLUMN_NAME_LEN] = '\0';
			colinfo.push_back(describe_col(cname, ctype, csize, cdd));
		}
	}

	void get(size_t i, int &value, bool &nflg) { 
		SQLLEN siz;	
		rc = SQLGetData(hstmt, (SQLUSMALLINT)i, SQL_C_SLONG, &value, 0, &siz);
		nflg = (siz == SQL_NULL_DATA);
	 }
	void get(size_t i, unsigned int &value, bool &nflg) {
		SQLLEN siz;	
		rc = SQLGetData(hstmt, (SQLUSMALLINT)i, SQL_C_ULONG, &value, 0, &siz);
		nflg = (siz == SQL_NULL_DATA);
	}
	void get(size_t i, double &value, bool &nflg) {
		SQLLEN siz;	
		rc = SQLGetData(hstmt, (SQLUSMALLINT)i, SQL_C_DOUBLE, &value, 0, &siz);
		nflg = (siz == SQL_NULL_DATA);
	}
private:
	void get_buffer(size_t i, size_t &readtotal, bool &nflg) {
		SQLLEN	rbufsize = 0;
		SQLSMALLINT typ = colinfo[i-1].typ;
		if ( typ == SQL_BINARY || typ == SQL_VARBINARY || typ == SQL_LONGVARBINARY ) {
			// バイナリデータの取得SQLGetDataを2回呼び出しているが効率が悪いのでバイナリデータの取得の時に使用  
			// 1回目は必要なバッファサイズを得る為にバッファサイズを0で呼び出す	
			rc = SQLGetData( hstmt, (SQLUSMALLINT)i, SQL_C_BINARY, &buff[0], 0, &rbufsize);
			if ( rc != SQL_NO_DATA && rbufsize != SQL_NULL_DATA ) {
				if ( (size_t)rbufsize > buff.size() ) { // バッファ長の設定
					buff.resize( rbufsize + 2, 0);
				}
				rc = SQLGetData( hstmt, (SQLUSMALLINT)i, SQL_C_BINARY, &buff[0], buff.size()-1, &rbufsize);
				readtotal = rbufsize;
			}
		} else {
			// 文字列の時は、適当なバッファサイズで一度取得してみる 
			// buff.size()-1としているのはMySQL ODBCドライバーがNULL文字を書き込むときにバッファ長を考慮していないような為 
			buff[buff.size()-1] = '\0';
			rc = SQLGetData( hstmt, (SQLUSMALLINT)i, SQL_C_CHAR, &buff[0], buff.size()-1, &rbufsize);
			if ( rc != SQL_NO_DATA && rbufsize != SQL_NULL_DATA ) {
				SQLINTEGER ofs = strlen( &buff[0]);
				if ( (size_t)rbufsize > buff.size()-1 || ofs < rbufsize ) { // バッファ長の設定
					buff.resize( rbufsize + 2, 0);
					// 2回目の取得時は、すでに読み込んだ文字の長さを考慮する（strlenで読みこんだ文字列長を取得する）  
					rc = SQLGetData( hstmt, (SQLUSMALLINT)i, SQL_C_CHAR, &buff[0] + ofs, buff.size() - ofs - 1, &rbufsize);
					readtotal = ofs + rbufsize;
				} else {
					readtotal = rbufsize;
				}
			}
		}
		if ( rbufsize == SQL_NULL_DATA  ) {
			// NULL は空文字にする
			buff[0] = '\0';
			readtotal = 1;
		}
		nflg = (rbufsize == SQL_NULL_DATA);
	}
public:
	void get(size_t i, std::string &str, bool &nflg) {
		size_t readtotal = 0;
		get_buffer(i, readtotal, nflg);
		str = std::string(buff.begin(), buff.begin() + readtotal);
	}

	bool fetch() {
		rc=SQLFetch(hstmt); 
		return rc == SQL_SUCCESS;
	};

private:
	kz_resultset resultset() {
		get_colinfo();
		kz_resultset	result;
		// SQLGetDataのiは1始まりだが、colinfoは0始まりで格納している。
		for ( int i = 1; (size_t)i < colinfo.size() + 1; i++ ) {
			size_t readtotal = 0;
			bool nflg = false;
			get_buffer(i, readtotal, nflg);
			result.insert( kz_resultset_pair(colinfo[i-1].name, std::string(buff.begin(), buff.begin() + readtotal)));
		}
		return result;
	}

	kz_resultset_array resultset_array() {
		kz_resultset_array	result;
		if ( iserr() ) return result;
		for (rc=SQLFetch(hstmt); rc == SQL_SUCCESS; rc=SQLFetch(hstmt)) {
			result.push_back( resultset() );
		}
		return result;
	}


	kz_string_array resultset_string() {
		kz_string_array	result;
		rc=SQLFetch(hstmt);
		if ( rc != SQL_SUCCESS ) return result;
		get_colinfo();
		// SQLGetDataのiは1始まりだが、colinfoは0始まりで格納している。
		for ( int i = 1; (size_t)i < colinfo.size() + 1; i++ ) {
			size_t readtotal = 0;
			bool nflg = false;
			get_buffer(i, readtotal, nflg);
			result.push_back(std::string(buff.begin(), buff.begin() + readtotal));
		}
		return result;
	}

public:
	kz_stmt() : con(0), rc(SQL_SUCCESS), hstmt(0), count(0), sql(""), buff(130), tsary(), colinfo() {}
	kz_stmt(kz_odbc	*con_) : con(con_), rc(SQL_SUCCESS), hstmt(0), count(0), sql(""), buff(130), tsary(), colinfo() {}
	~kz_stmt() { /*freestmt();*/ } // connectionと同期を取っていないのでこれが呼ばれたときに落ちる場合がある（stmtの開放が問題になる場合は直接freestmtを呼び出すようにすること。
	void setODBC(kz_odbc *con_) { con = con_; }

	kz_stmt& exec(const char *sql ) { execdirect(sql); return *this; }
	kz_resultset_array select( const char *sql ) { execdirect(sql); return resultset_array(); }
	kz_resultset_array tables() { tables_(); return resultset_array(); }
	kz_resultset_array columns( const char *table) { columns_(table); return resultset_array(); }
	kz_stmt& operator , ( const std::string &param) { return *this, param.c_str(); }
	kz_stmt& operator , ( const char *param ) { prepare(param);	bind( SQL_C_CHAR, SQL_VARCHAR, param); return *this; }
	kz_stmt& operator , ( const unsigned  char &param ) { bind( SQL_C_UTINYINT, SQL_CHAR, &param); return *this; }
	kz_stmt& operator , ( const short &param ) { bind( SQL_C_SSHORT, SQL_SMALLINT, &param); return *this; }
	kz_stmt& operator , ( const unsigned short &param ) { bind( SQL_C_USHORT, SQL_SMALLINT, &param); return *this; }
	kz_stmt& operator , ( const int &param ) { bind( SQL_C_SLONG, SQL_INTEGER, &param); return *this; }
	kz_stmt& operator , ( const unsigned int &param ) { bind( SQL_C_ULONG, SQL_INTEGER, &param); return *this; }
	kz_stmt& operator , ( const long &param ) { bind( SQL_C_SLONG, SQL_INTEGER, &param); return *this; }
	kz_stmt& operator , ( const unsigned long &param ) { bind( SQL_C_ULONG, SQL_INTEGER, &param); return *this; }
	kz_stmt& operator , ( const long long &param ) { bind( SQL_C_SBIGINT, SQL_BIGINT, &param); return *this; }
	kz_stmt& operator , ( const float &param ) { bind( SQL_C_FLOAT, SQL_FLOAT, &param); return *this; }
	kz_stmt& operator , ( const double &param ) { bind( SQL_C_DOUBLE, SQL_DOUBLE, &param); return *this; }
	kz_stmt& operator , ( const struct tm &param ) { 
		tsary.push_back(TIMESTAMP_STRUCT());
		TIMESTAMP_STRUCT &ts = tsary.back();
		ts.year = param.tm_year + 1900;
		ts.month = param.tm_mon + 1;
		ts.day = param.tm_mday;
		ts.hour = param.tm_hour;
		ts.minute = param.tm_min;
		ts.second = param.tm_sec;
		bind( SQL_C_TIMESTAMP, SQL_TIMESTAMP, &(tsary.back()));
		return *this; 
	}
	kz_stmt& operator , ( const struct tm *param ) { return *this, *param; }
	kz_stmt& operator , (const kz_odbc_marker_end_sql& dmy) { execute(); return *this; }
	kz_resultset_array operator , (const kz_odbc_marker_end_sql_resultsets& dmy) { execute(); return resultset_array(); }
	kz_stmt& operator , ( const kz_odbc_maker_null& dmy) { bind_null(); return *this; }

	kz_string_array next() { return resultset_string();}
	std::vector< describe_col > &cinfo() { get_colinfo(); return this->colinfo;}

	kz_string_array errors() {
		kz_string_array	result;
		char			state[6];
		SQLINTEGER		nerr;
		char			msg[SQL_MAX_MESSAGE_LENGTH+1];
		SQLSMALLINT		msglen;
		msg[SQL_MAX_MESSAGE_LENGTH] = '\0';
		
		while ( SQLError( con->henv, con->hdbc, hstmt, (SQLCHAR*)state, &nerr, (SQLCHAR*)msg, sizeof(msg), &msglen) == SQL_SUCCESS ) {
			std::strstream err;
			err << state << "(" << nerr << "):" << msg;
			result.push_back( std::string(std::istreambuf_iterator<char>(err), std::istreambuf_iterator<char>()));
		}
		if ( !result.empty() && hstmt && sql != 0 ) {
			result.push_back( std::string("Last sql:") + sql);
		}
		return result;
	}

};

#endif
