/*	
 *	SqliteHelper.c
 *
 *	SQLiteサポートモジュール
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2015-2016 Naoyuki Sawa
 *
 *	* Sun Jul 19 17:54:39 JST 2015 Naoyuki Sawa
 *	- Win32プロジェクト用のSQLiteサポートモジュールです。
 *	  test.win/extclip.hを参照してください。
 *	- 現時点では、P/ECEプロジェクトでは使用しません。
 *	  もし将来、P/ECEでSQLiteが動作するように出来たら、使用するかも知れません。
 *	* Fri Mar 11 21:58:48 JST 2016 Naoyuki Sawa
 *	- ファイル名を、recordset.h,recordset.c⇒SqliteHelper.h,SqliteHelper.cに変更しました。
 *	  ファイル名を変更した理由は、当モジュールがSQLite用のサポートモジュールである事を、判り易くするためです。
 */
#include "clip.h"
/****************************************************************************
 *	
 ****************************************************************************/
//SQLiteにはsqlite_compile()のvprintf版が無いので、利便性のために追加した。
int sqlite_compile_vprintf(sqlite* db, const char* sqlFormat, const char** pzTail, sqlite_vm** ppVm, char** pzErrmsg, va_list ap) {
	int rc;
	char* zSql;
	zSql = sqlite_vmprintf(sqlFormat, ap);
	rc = sqlite_compile(db, zSql, pzTail, ppVm, pzErrmsg);
	sqlite_freemem(zSql);
	return rc;
}
/*--------------------------------------------------------------------------*/
//SQLiteにはsqlite_compile()のprintf版が無いので、利便性のために追加した。
int sqlite_compile_printf(sqlite* db, const char* sqlFormat, const char** pzTail, sqlite_vm** ppVm, char** pzErrmsg, ...) {
	int rc;
	va_list ap;
	va_start(ap, pzErrmsg);
	rc = sqlite_compile_vprintf(db, sqlFormat, pzTail, ppVm, pzErrmsg, ap);
	va_end(ap);
	return rc;
}
/****************************************************************************
 *	
 ****************************************************************************/
//レコードセットを開き、最初のレコードを返す。
//最初のレコードが無かった場合は、NULLを返す。
ST_Recordset_r* Recordset_Open(sqlite* db, const char* sSql, ...) {
	int rc;
	char* sErr;
	va_list ap;
	//レコードセットのメモリを確保する。
	ST_Recordset_r* rs = calloc(1, sizeof(ST_Recordset_r));
	if(!rs) { DIE(); }
	//仮想マシンを作成する。
	va_start(ap, sSql);
	rc = sqlite_compile_vprintf(db, sSql, NULL, &rs->vm, &sErr, ap);
	if(rc) { die("Recordset_Open:%s", sErr); }	//SQLエラー
	va_end(ap);
	//最初のレコードを取得する。
	//最初のレコードが有れば、レコードセットを返す。
	//最初のレコードセットが無ければ、レコードセットを削除してNULLを返す。
	return Recordset_Next(rs);
}
/*--------------------------------------------------------------------------*/
//レコードセットを閉じる。
//Recordset_Next()がNULLを返す前に中止する場合は、当関数を呼び出して下さい。
void Recordset_Close(ST_Recordset_r* rs) {
	int rc;
	char* sErr;
	//NULLポインタに対して呼び出された場合は、何もしない。
	if(!rs) { return; }
	//仮想マシンを削除する。
	rc = sqlite_finalize(rs->vm, &sErr);
	if(rc) { die("Recordset_Close:%s", sErr); }
	//レコードセットのメモリを開放する。
	free(rs);
}
/*--------------------------------------------------------------------------*/
//次のレコードを返す。
//次のレコードが無ければ、レコードセットを削除し、NULLを返す。
ST_Recordset_r* Recordset_Next(ST_Recordset_r* rs) {
	int rc;
	char* sErr;
	//次のレコードを取得する。
	rc = sqlite_step(rs->vm, &rs->nCol, &rs->aVal, &rs->aCol);
	//次のレコードが有れば、レコードセットを返す。
	if(rc == SQLITE_ROW) { return rs; }
	//次のレコードが無ければ…
	if(rc == SQLITE_DONE) {
		//レコードセットを削除し、NULLを返す。
		Recordset_Close(rs);
		return NULL;
	}
	//それ以外ならば、エラーである。
	//エラーメッセージを取得するために仮想マシンを削除し、エラーメッセージを表示する。
	rc = sqlite_finalize(rs->vm, &sErr);
	if(!rc) { DIE(); }	//sqlite_step()がエラーを返したのに、sqlite_finalize()が成功する事は、起こり得ないはず。
	die("Recordset_Next:%s", sErr);
}
/*--------------------------------------------------------------------------*/
//カラム数を返す。
int Recordset_Count(ST_Recordset_r* rs) {
	//カラム数を返す。
	return rs->nCol;
}
/*--------------------------------------------------------------------------*/
//カラム名に対応する、カラムインデクスを取得する。
int Recordset_ColNI(ST_Recordset_r* rs, const char* sNam) {			//Name→Index
	int iIdx;
	//カラム名が一致するカラムを検索する。
	for(iIdx = 0; iIdx < rs->nCol; iIdx++) {
		//カラム名が一致するカラムが見つかったら、カラムインデクスを返す。
		if(!strcasecmp(rs->aCol[iIdx], sNam)) { return iIdx; }
	}
	DIE();	//カラム名が一致するカラムが見つからなかったら、エラー停止する。
}
/*--------------------------------------------------------------------------*/
//可変引数リストによる書式付きカラム名に対応する、カラムインデクスを取得する。
int Recordset_ColVI(ST_Recordset_r* rs, const char* sFmt, va_list ap) {		//va_list→Index
	int iIdx;
	//カラム名を作成する。
	sFmt = strdup_vprintf(sFmt, ap);
	//カラムインデクスを取得する。
	iIdx = Recordset_ColNI(rs, sFmt);
	//カラム名を開放する。
	free((char*)sFmt);
	//カラムインデクスを返す。
	return iIdx;
}
/*--------------------------------------------------------------------------*/
//書式付きカラム名に対応する、カラムインデクスを取得する。
int Recordset_ColFI(ST_Recordset_r* rs, const char* sNam, ...) {		//Format→Index
	int iIdx;
	va_list ap;
	//カラムインデクスを取得する。
	va_start(ap, sNam);
	iIdx = Recordset_ColVI(rs, sNam, ap);
	va_end(ap);
	//カラムインデクスを返す。
	return iIdx;
}
/*--------------------------------------------------------------------------*/
//カラムインデクスに対応する、カラム名を取得する。
const char* Recordset_ColIN(ST_Recordset_r* rs, int iIdx) {			//Index→Name
	//カラム数の範囲外ならば、エラー停止する。
	if((unsigned)iIdx >= (unsigned)rs->nCol) { DIE(); }
	//カラム名を返す。
	return rs->aCol[iIdx];
}
/*--------------------------------------------------------------------------*/
//カラムインデクス指定で、数値を取得する。
int Recordset_GetIN(ST_Recordset_r* rs, int iIdx) {				//Index→Numeric
	const char* sTxt;
	//テキストを取得する。NULLの可能性も有る。
	sTxt = Recordset_GetIT(rs, iIdx);
	//テキストを数値に変換して返す。
	//NULLか,又は,数値として解釈できない場合は、(0)を返す。
	return sTxt ? atoi(sTxt) : 0;
}
/*--------------------------------------------------------------------------*/
//カラム名指定で、数値を取得する。
int Recordset_GetNN(ST_Recordset_r* rs, const char* sNam) {			//Name→Numeric
	const char* sTxt;
	//テキストを取得する。NULLの可能性も有る。
	sTxt = Recordset_GetNT(rs, sNam);
	//テキストを数値に変換して返す。
	//NULLか,又は,数値として解釈できない場合は、(0)を返す。
	return sTxt ? atoi(sTxt) : 0;
}
/*--------------------------------------------------------------------------*/
//可変引数リストによる書式付きカラム名指定で、数値を取得する。
int Recordset_GetVN(ST_Recordset_r* rs, const char* sFmt, va_list ap) {		//va_list→Numeric
	int iNum;
	//カラム名を作成する。
	sFmt = strdup_vprintf(sFmt, ap);
	//数値を取得する。
	iNum = Recordset_GetNN(rs, sFmt);
	//カラム名を開放する。
	free((char*)sFmt);
	//数値を返す。
	return iNum;
}
/*--------------------------------------------------------------------------*/
//書式付きカラム名指定で、数値を取得する。
int Recordset_GetFN(ST_Recordset_r* rs, const char* sFmt, ...) {		//Format→Numeric
	int iNum;
	va_list ap;
	//数値を取得する。
	va_start(ap, sFmt);
	iNum = Recordset_GetVN(rs, sFmt, ap);
	va_end(ap);
	//数値を返す。
	return iNum;
}
/*--------------------------------------------------------------------------*/
//カラムインデクス指定で、テキストを取得する。
//NULLを返す可能性も有る。
const char* Recordset_GetIT(ST_Recordset_r* rs, int iIdx) {			//Index→Text
	//カラム数の範囲外ならば、エラー停止する。
	if((unsigned)iIdx >= (unsigned)rs->nCol) { DIE(); }
	//テキストを返す。NULLの可能性も有る。
	return rs->aVal[iIdx];
}
/*--------------------------------------------------------------------------*/
//カラム名指定で、テキストを取得する。
//NULLを返す可能性も有る。
const char* Recordset_GetNT(ST_Recordset_r* rs, const char* sNam) {		//Name→Text
	//カラム名に対応する、カラムインデクスを取得する。
	int iIdx = Recordset_ColNI(rs, sNam);
	//テキストを返す。NULLの可能性も有る。
	return Recordset_GetIT(rs, iIdx);
}
/*--------------------------------------------------------------------------*/
//可変引数リストによる書式付きカラム名指定で、テキストを取得する。
//NULLを返す可能性も有る。
const char* Recordset_GetVT(ST_Recordset_r* rs, const char* sFmt, va_list ap) {	//va_list→Text
	const char* sTxt;
	//カラム名を作成する。
	sFmt = strdup_vprintf(sFmt, ap);
	//テキストを取得する。
	sTxt = Recordset_GetNT(rs, sFmt);
	//カラム名を開放する。
	free((char*)sFmt);
	//テキストを返す。
	return sTxt;
}
/*--------------------------------------------------------------------------*/
//書式付きカラム名指定で、テキストを取得する。
//NULLを返す可能性も有る。
const char* Recordset_GetFT(ST_Recordset_r* rs, const char* sFmt, ...) {	//Format→Text
	const char* sTxt;
	va_list ap;
	//テキストを取得する。
	va_start(ap, sFmt);
	sTxt = Recordset_GetVT(rs, sFmt, ap);
	va_end(ap);
	//テキストを返す。
	return sTxt;
}
/****************************************************************************
 *	
 ****************************************************************************/
//レコードを追加,又は,更新するための、空のレコードセットを作成する。
//テーブル名だけを指定する。
ST_Recordset_w* Recordset_New(sqlite* db, const char* sTbl) {
	//レコードセットのメモリを確保する。
	ST_Recordset_w* rs = calloc(1, sizeof(ST_Recordset_w));
	if(!rs) { DIE(); }
	//データベースとカラム名を格納する。
	rs->db   = db;
	rs->sTbl = strdup(sTbl);
	//ハッシュテーブルを作成する。
	rs->ht   = ght_create(0);	//初期サイズ指定無し
	ght_set_rehash(rs->ht, 1);	//自動リサイズ有効
	//レコードセットを返す。
	return rs;
}
/*--------------------------------------------------------------------------*/
//レコードの追加,又は,更新をキャンセルする。
//Recordset_Insert(),又は,Recordset_Update()を呼び出さない場合は、当関数を呼び出して下さい。
void Recordset_Cancel(ST_Recordset_w* rs) {
	ght_iterator_t it;
	char *sTxt, *sNam;
	//sqlite_mprintf()で作成したテキストを開放する。
	for(sTxt = ght_first(rs->ht, &it, (const void**)&sNam);
	    sTxt;
	    sTxt = ght_next( rs->ht, &it, (const void**)&sNam)) { sqlite_freemem(sTxt); }
	//ハッシュテーブルを削除する。
	ght_finalize(rs->ht);
	//カラム名を削除する。
	free(rs->sTbl);
	//レコードセットのメモリを開放する。
	free(rs);
}
/*--------------------------------------------------------------------------*/
//新規レコードを追加する。
void Recordset_Insert(ST_Recordset_w* rs) {
	int rc;
	ght_iterator_t it;
	char *sSql, *sSep, *sTmp;
	char *sTxt, *sNam, *sErr;
	//SQLを作成する。
	sSql = strdup_printf("INSERT INTO %s (", rs->sTbl);			//確保1
	sSep = "";
	for(sTxt = ght_first(rs->ht, &it, (const void**)&sNam);
	    sTxt;
	    sTxt = ght_next( rs->ht, &it, (const void**)&sNam)) {
		sTmp = strconcat(sSql, sSep, sNam, NULL);			//確保2
		free(sSql);							//開放1
		sSql = sTmp;
		sSep = ",";
	}
	sTmp = strconcat(sSql, ") VALUES (", NULL);				//確保3
	free(sSql);								//開放2
	sSql = sTmp;
	sSep = "";
	for(sTxt = ght_first(rs->ht, &it, (const void**)&sNam);
	    sTxt;
	    sTxt = ght_next( rs->ht, &it, (const void**)&sNam)) {
		sTmp = strconcat(sSql, sSep, sTxt, NULL);			//確保4
		free(sSql);							//開放3
		sSql = sTmp;
		sSep = ",";
	}
	sTmp = strconcat(sSql, ")", NULL);					//確保5
	free(sSql);								//開放4
	sSql = sTmp;
	//SQLを実行する。
	rc = sqlite_exec(rs->db, sSql, NULL, NULL, &sErr);
	if(rc) { die("Recordset_Insert:%s", sErr); }	//SQLエラー
	free(sSql);								//開放5
	//レコードセットを開放する。
	Recordset_Cancel(rs);
}
/*--------------------------------------------------------------------------*/
//既存レコードを更新する。
void Recordset_Update(ST_Recordset_w* rs, const char* sWhere/*NULL可*/, ...) {
	int rc;
	ght_iterator_t it;
	char *sSql, *sSep, *sTmp;
	char *sTxt, *sNam, *sErr;
	va_list ap;
	//SQLを作成する。
	sSql = strdup_printf("UPDATE %s SET ", rs->sTbl);			//確保1
	sSep = "";
	for(sTxt = ght_first(rs->ht, &it, (const void**)&sNam);
	    sTxt;
	    sTxt = ght_next( rs->ht, &it, (const void**)&sNam)) {
		sTmp = strconcat(sSql, sSep, sNam, "=", sTxt, NULL);		//確保2
		free(sSql);							//開放1
		sSql = sTmp;
		sSep = ",";
	}
	if(sWhere) {	//WHERE句の指定が有れば…
		va_start(ap, sWhere);
		sWhere = sqlite_vmprintf(sWhere, ap);				//確保3
		va_end(ap);
		sTmp = strconcat(sSql, " WHERE ", sWhere, NULL);		//確保4
		sqlite_freemem((char*)sWhere);					//確保3
		free(sSql);							//開放2
		sSql = sTmp;
	}
	//SQLを実行する。
	rc = sqlite_exec(rs->db, sSql, NULL, NULL, &sErr);
	if(rc) { die("Recordset_Insert:%s", sErr); }	//SQLエラー
	free(sSql);								//開放4
	//レコードセットを開放する。
	Recordset_Cancel(rs);
}
/*--------------------------------------------------------------------------*/
//カラム名指定で、数値を設定する。
void Recordset_SetNN(ST_Recordset_w* rs, int iNum, const char* sNam) {					//Name←Numeric
	//数値の文字列を作成する。
	char* s = sqlite_mprintf("%d", iNum);
	//カラム名の長さを取得する。(終端のヌル文字も含む)
	int n = strlen(sNam) + 1/*nul*/;
	//カラム名に対応する文字列を設定する。設定出来たらここまで。
	if(!ght_insert(rs->ht, s, n, sNam)) { return; }
	//カラム名に対応する文字列が既に有れば、文字列を置き換える。
	s = ght_replace(rs->ht, s, n, sNam);
	//古い文字列を開放する。
	sqlite_freemem(s);
}
/*--------------------------------------------------------------------------*/
//可変引数リストによる書式付きカラム名指定で、数値を設定する。
void Recordset_SetVN(ST_Recordset_w* rs, int iNum, const char* sFmt, va_list ap) {			//va_list←Numeric
	//カラム名を作成する。
	sFmt = strdup_vprintf(sFmt, ap);
	//数値を設定する。
	Recordset_SetNN(rs, iNum, sFmt);
	//カラム名を開放する。
	free((char*)sFmt);
}
/*--------------------------------------------------------------------------*/
//書式付きカラム名指定で、数値を設定する。
void Recordset_SetFN(ST_Recordset_w* rs, int iNum, const char* sFmt, ...) {				//Format←Numeric
	va_list ap;
	//数値を設定する。
	va_start(ap, sFmt);
	Recordset_SetVN(rs, iNum, sFmt, ap);
	va_end(ap);
}
/*--------------------------------------------------------------------------*/
//カラム名指定で、テキストを設定する。
void Recordset_SetNT(ST_Recordset_w* rs, const char* sTxt/*NULL可*/, const char* sNam) {		//Name←Text
	//'テキスト',又は,NULLという文字列を作成する。
	char* s = sqlite_mprintf("%Q", sTxt);
	//カラム名の長さを取得する。(終端のヌル文字も含む)
	int n = strlen(sNam) + 1/*nul*/;
	//カラム名に対応する文字列を設定する。設定出来たらここまで。
	if(!ght_insert(rs->ht, s, n, sNam)) { return; }
	//カラム名に対応する文字列が既に有れば、文字列を置き換える。
	s = ght_replace(rs->ht, s, n, sNam);
	//古い文字列を開放する。
	sqlite_freemem(s);
}
/*--------------------------------------------------------------------------*/
//可変引数リストによる書式付きカラム名指定で、テキストを設定する。
void Recordset_SetVT(ST_Recordset_w* rs, const char* sTxt/*NULL可*/, const char* sFmt, va_list ap) {	//va_list←Text
	//カラム名を作成する。
	sFmt = strdup_vprintf(sFmt, ap);
	//テキストを設定する。
	Recordset_SetNT(rs, sTxt, sFmt);
	//カラム名を開放する。
	free((char*)sFmt);
}
/*--------------------------------------------------------------------------*/
//書式付きカラム名指定で、テキストを設定する。
void Recordset_SetFT(ST_Recordset_w* rs, const char* sTxt/*NULL可*/, const char* sFmt, ...) {		//Format←Text
	va_list ap;
	//テキストを設定する。
	va_start(ap, sFmt);
	Recordset_SetVT(rs, sTxt, sFmt, ap);
	va_end(ap);
}
/*--------------------------------------------------------------------------*/
//カラム名指定で、式を設定する。
void Recordset_SetNE(ST_Recordset_w* rs, const char* sExp, const char* sNam) {				//Name←Expr
	//式の文字列を作成する。
	char* s = sqlite_mprintf("%s", sExp);	//"%q"ではない事に注意せよ。"%q"にすると例えば「Recordset_SetNE(rs,"val||'.'","val")」の式が「val||''.''」に展開されてしまいエラーになる。
	//カラム名の長さを取得する。(終端のヌル文字も含む)
	int n = strlen(sNam) + 1/*nul*/;
	//カラム名に対応する文字列を設定する。設定出来たらここまで。
	if(!ght_insert(rs->ht, s, n, sNam)) { return; }
	//カラム名に対応する文字列が既に有れば、文字列を置き換える。
	s = ght_replace(rs->ht, s, n, sNam);
	//古い文字列を開放する。
	sqlite_freemem(s);
}
/*--------------------------------------------------------------------------*/
//可変引数リストによる書式付きカラム名指定で、式を設定する。
void Recordset_SetVE(ST_Recordset_w* rs, const char* sExp, const char* sFmt, va_list ap) {		//va_list←Expr
	//カラム名を作成する。
	sFmt = strdup_vprintf(sFmt, ap);
	//式を設定する。
	Recordset_SetNE(rs, sExp, sFmt);
	//カラム名を開放する。
	free((char*)sFmt);
}
/*--------------------------------------------------------------------------*/
//書式付きカラム名指定で、式を設定する。
void Recordset_SetFE(ST_Recordset_w* rs, const char* sExp, const char* sFmt, ...) {			//Format←Expr
	va_list ap;
	//式を設定する。
	va_start(ap, sFmt);
	Recordset_SetVE(rs, sExp, sFmt, ap);
	va_end(ap);
}
