データベースのデータを取得するのは、CやC++で実装するのは中々面倒です。
何故かと言うと、DBの種類によってやり方が違うからです。
また、フィールドの型によっても取得の仕方が違うため、とても面倒です。
とりあえず、ここでは以下の環境でのサンプルを見てみましょう。
<環境>
OS:Windows XP
DB:PostgresSQL 8.1
<テーブル>
CREATE TABLE test
(
a int2 NOT NULL,
b1 int4,
b2 int8,
b4 varchar(10),
b5 float4,
b6 float8,
b7 date,
b8 timestamp,
CONSTRAINT pkey_t_test PRIMARY KEY (a)
)
<データ>
insert into test values(3, 1234,12345678, 'abcdef', 123.456, 1234567890.1
,'2008-04-07', '2008-04-07 11:12:15');
【準備】
以下の設定を事前にしてください。
1.「C++でPostgresと接続するには? 」を参照して、コンパイルの設定をしてください。
もし、Dev-C++、VC++以外のコンパイラの場合は、「PostgreSQLの環境構築 」を参照して、
makefileなどの設定を行ってください。
2.boostをインストールしてください。
ファイルコピーするだけです。こちら
参考。
【サンプル】
#include <stdio.h>
#include <stdlib.h>
#include <map>
#include <algorithm>
#include <stdexcept>
#include <sys/types.h>
#include "boost/cstdint.hpp"
#include "libpq-fe.h"
/* ntohl/htonl用 */
#include <netinet/in.h>
#include <arpa/inet.h>
//こちらの記事
をコピーして以下のファイル名で保存してください
#include "field.h
"
/*
pg_type.hから定義を抜き出しました。
本当は、これ用の.hファイルを作成してincludeした方がいいです。
*/
#define INT8OID 20
#define INT2OID 21
#define INT4OID 23
#define FLOAT4OID 700
#define FLOAT8OID 701
#define VARCHAROID 1043
#define DATEOID 1082
#define TIMESTAMPOID 1114
using namespace std;
using namespace boost;
namespace nana{
short transPgInt2(const char* val)
{
unsigned short st = ntohs(*(unsigned short *) val);
return *(short*)&st;
};
long transPgInt4(const char* val)
{
uint32_t st = ntohl(*((uint32_t*) val));
return *(long*)&st;
};
float transPgFloat4(const char* val)
{
unsigned int st = ntohl(*(uint32_t*)val);
return *(float*)&st;
};
void transPg8byte(char res[8], const char* val){
*(uint32_t*)&res[4] = ntohl(*(uint32_t*)val);
*(uint32_t*)&res[0] = ntohl(*(uint32_t*)(val+4));
}
///long long 型が使用できない場合はここはコメントアウトしてください。
double transPgInt8(const char* val)
{
char tmp[8];
transPg8byte(tmp, val);
return (double)*(long long*)tmp;
};
double transPgFloat8(const char* val)
{
char tmp[8];
transPg8byte(tmp, val);
return *(double*)tmp;
};
time_t transPgDate(const char* val)
{
unsigned int st = ntohl(*((uint32_t*) val));
tm tmp;
tmp.tm_sec =0;
tmp.tm_min = 0;
tmp.tm_hour = 0;
tmp.tm_mday = st+1;
tmp.tm_mon = 0;
tmp.tm_year = 100;
tmp.tm_isdst = -1;
time_t t = mktime(&tmp);
return t;
};
/**
TimeStampの変換方法はマニュアルのどこにも記述無いです。
思考錯誤で以下のコード書いてるので間違ってるかも。。
*/
time_t transPgTimeStamp(const char* val)
{
char cval[8];
transPg8byte(cval, val);
//
double d = *(double*)cval;
tm tmp;
tmp.tm_sec =static_cast<int>(d);
tmp.tm_min = 0;
tmp.tm_hour = 0;
tmp.tm_mday = 1;
tmp.tm_mon = 0;
tmp.tm_year = 100;
tmp.tm_isdst = -1;
time_t t = mktime(&tmp);
return t;
};
/**
DBの型とOSの型をマッピングする。<br>
Postgres7以前と8以降で取得の仕方が違います。<br>
7以前は例えばlongの場合、以下のようです。<br>
long val = *(long*)PQgetvalue(res, rline, i);<br>
8以降では、ntohlを使用しないといけないです。
*/
const FieldPtr transPgField2Field(PGresult* res, int rline, int i){
Oid type = PQftype(res, i);
int pq_size = PQfsize(res, i);
char* val = PQgetvalue(res, rline, i);
//NULL対応
if(PQgetisnull(res, rline, i) == 1){
return FieldPtr((Field*)new FieldNull());
}
//DBの型とC++の型をマッピングする
FieldPtr fptr;
//
switch(type){
case INT2OID:
fptr = createFieldPtr<int>(transPgInt2(val));
break;
case INT4OID:
fptr = createFieldPtr<long>(transPgInt4(val));
break;
case INT8OID:
fptr = createFieldPtr<double>(transPgInt8(val));
break;
case FLOAT4OID:
fptr = createFieldPtr<float>(transPgFloat4(val));
break;
case FLOAT8OID:
fptr = createFieldPtr<double>(transPgFloat8(val));
break;
case VARCHAROID:
fptr = createFieldPtr<string>(val);
break;
case DATEOID:
fptr = createFieldPtr<DateTime>(DateTime(transPgDate(val)));
break;
case TIMESTAMPOID:
fptr = createFieldPtr<DateTime>(DateTime(transPgTimeStamp(val)));
break;
default:
throw runtime_error("用意していた型以外が使用された");
}
return fptr;
};
}; //namespace nana
static void
exit_nicely(PGconn *conn)
{
PQfinish(conn);
system("PAUSE");
exit(1);
}
//メイン
int main(int argc, char **argv)
{
PGconn *conn;
PGresult *res;
conn = PQsetdbLogin(NULL,
NULL,
NULL,
NULL,
"dbname", //dbname 作成したDB名を設定してください
"postgres", //login
"postgres"); //pwd postgresユーザのパスワードを書いてください。
/* バックエンドとの接続確立に成功したかを確認する */
if (PQstatus(conn) != CONNECTION_OK)
{
fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn));
exit_nicely(conn);
}
char* strSQL = "select * from test";
res = PQexecParams(conn,
strSQL,
0, /* パラメータは0個。 */
NULL,
NULL,
NULL,
NULL,
1); /* バイナリ結果を要求。 */
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
fprintf(stderr, "SELECT failed: %s", PQerrorMessage(conn));
PQclear(res);
exit_nicely(conn);
}
try{
cout
<< nana::transPgField2Field(res, 0,0)->getString() << " : "
<< nana::transPgField2Field(res, 0,1)->getString() << " : "
<< nana::transPgField2Field(res, 0,2)->getString() << " : "
<< nana::transPgField2Field(res, 0,3)->getString() << " : "
<< nana::transPgField2Field(res, 0,4)->getString() << " : "
<< nana::transPgField2Field(res, 0,5)->getString() << " : "
<< nana::transPgField2Field(res, 0,6)->getTime().ctime() << " : "
<< nana::transPgField2Field(res, 0,7)->getTime().ctime() << endl;
PQclear(res);
}catch(exception& e){
cout << e.what() << endl;
}
system("PAUSE");
return EXIT_SUCCESS;
}
【出力】
1 : 123456 : 12345678 : abcdefg : 12.345 : 12345.67 : Fri Feb 29 00:00:00 2008
: Fri Apr 04 10:11:12 2008
【説明】
ここでのサンプルは、PostgresのSQLの型と、C++の型をどのように変換するか?のサンプルです。
ですので読まれた方は、「ふ~ん。」って感じかもしれませんね。。
でも、Postgresのマニュアルでは、INT2, INT4とVARCHARの変換方法しか記述していません。
ですので上記のサンプルが参考になれば幸いです。
一応、プログラム上の工夫を記述しておきます。
「型を気にせずに使用できるクラスを作るには? 」で作成した、Fieldクラスを使用しています。
main()を見ると分かるのですが、これにより、型をそれほど気にせずに使用できています。
SQLで返ってくる型はテーブルによって違います。
つまり、C++でデータを受け取る型も違います。
それを1つの関数の返り値として返却するのは普通は難しいのは分かりますよね。
例えば、DBが整数型のときint型を返し、DBが文字列型のときstring型を返す関数を作れないですよね。
でも、Fieldクラスを使用するとそれに近いことができます。
Fieldクラスのサンプルでもあると思います。
上記サンプルは、型の変換方法がマニュアルに記載されていないのでとても不安です。
もしかしたら、型はDBがあるOSによっても変わるかも知れません。
ですが、そこそこ遊べると思いますのでぜひぜひ色々遊んでみてください!
参考:
・Dev-C++ や wxDev-C++ でDBを使用するコンパイル設定をするには?
・型を気にせずに使用できるクラスを作るには?