출처 : API 완전정복
------------------------------------------------------------------------------------
[ CQuery.h ]
#include <sql.h>
#include <sqlext.h>
class CQuery
{
public:
// 최대 컬럼수, BLOB 입출력 단위, NULL 필드값
enum { MAXCOL=100, BLOBBATCH=10000, CQUERYNULL=-100, CQUERYEOF=-101,
CQUERYNOCOL=-102, CQUERYERROR=-103 };
private:
SQLHENV hEnv; // 환경 핸들
SQLHDBC hDbc; // 연결 핸들
SQLRETURN ret; // 최후 실행한 SQL문의 결과값
TCHAR Col[MAXCOL][255]; // 바인딩될 컬럼 정보
int FindCol(TCHAR *name); // 컬럼의 이름으로부터 번호를 찾아준다.
public:
SQLINTEGER AffectCount; // 영향받은 레코드 개수
SQLHSTMT hStmt; // 명령 핸들. 직접 사용 가능
SQLSMALLINT nCol; // 컬럼 개수
SQLCHAR ColName[MAXCOL][50]; // 컬럼의 이름들
SQLINTEGER lCol[MAXCOL]; // 컬럼의 길이/상태 정보
void PrintDiag(); // 진단 정보 출력
CQuery(); // 생성자
~CQuery(); // 파괴자:연결 핸들을 해제한다.
// 데이터 소스에 연결한다.
BOOL Connect(int Type, TCHAR *ConStr, TCHAR *UID=NULL, TCHAR *PWD=NULL);
BOOL Exec(LPCTSTR szSQL); // SQL문을 실행한다.
int ExecGetInt(LPCTSTR szSQL); // SQL문을 실행하고 첫번째 컬럼의 정수 읽음
// SQL문을 실행하고 첫번째 컬럼의 문자열 읽음
void ExecGetStr(LPCTSTR szSQL, TCHAR *buf);
SQLRETURN Fetch(); // 한 행 가져오기
void Clear(); // 커서 해제 및 언 바인딩
int GetInt(int nCol); // 정수형 컬럼 읽기
int GetInt(TCHAR *sCol); // 정수형 컬럼 읽기
void GetStr(int nCol, TCHAR *buf); // 문자열형 컬럼 읽기
void GetStr(TCHAR *sCol, TCHAR *buf); // 문자열형 컬럼 읽기
int ReadBlob(LPCTSTR szSQL, void *buf);
void WriteBlob(LPCTSTR szSQL, void *buf, int size)
};
------------------------------------------------------------------------------------
[ CQuery.cpp ]
// API 프로젝트에 사용될 경우 다음 두 줄을 포함시켜 주면 된다.
#include <windows.h>
#include <db\CQuery.h>
// MFC 프로젝트에 사용될 경우 stdafx.h에 CQuery.h를 포함한다.
#include "stdafx.h"
///////////////////////////////////////////////////////////////////////////////
/*
CQuery 클래스
: ODBC의 래퍼 클래스. API 수준에서 재사용 가능하도록 작성하였다.
SQL문을 편리하게 실행하고 결과를 쉽게 읽는데 초점을 맞추었으며 SQL 서버와 엑세스에서
완벽하게 테스트되었다. 255자 이하의 정수, 문자열 컬럼에 대해서만 사용 가능하며 그 이상의
길이를 가지는 필드는 "오른쪽 잘림" 진단 경고가 발생하므로 이 클래스로 읽을 수 없으며
ODBC API 함수를 직접 사용해야 한다. 최대 컬럼 개수는 100개이다.
초과시 사정없이 에러 메시지 박스를 출력하므로 이 한도를 알아서 넘지 않도록 주의해야 한다.
특히 필드수가 많은 테이블을 select * 로 읽는 것은 삼가하는 것이 좋다.
전진 전용 커서를 사용하므로 이미 읽은 레코드는 쿼리를 다시 실행해야만 읽을 수 있다.
사용법
1. CQuery 클래스의 객체를 생성한다. 가급적이면 전역으로 선언하는 것이 좋으며
필요한 수만큼 객체를 만든다. 보통 세 개 정도면 충분하다.
2. 적절한 Connect 함수를 호출하여 데이터 소스에 연결한다.
3. Exec 함수로 SQL문을 실행한다. 에러 처리는 Exec 함수 내부에서 처리한다.
4. Fetch 함수로 결과 셋 하나를 가져온다.
여러개의 결과셋이 있는 경우는 while 루프를 돌면서 차례대로 꺼내올 수 있다.
5. Get* 함수로 컬럼 값을 읽는다.
6. Clear 함수로 명령 핸들을 닫는다. (Select문일 경우만)
7. 객체를 파괴한다. 전역 객체인 경우는 일부러 파괴할 필요가 없다.
*/
// 생성자:각종 초기화를 담당한다.
CQuery::CQuery()
{
AffectCount=-1;
ret = SQL_SUCCESS;
// 환경 핸들을 할당하고 버전 속성을 설정한다.
SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&hEnv);
SQLSetEnvAttr( hEnv,SQL_ATTR_ODBC_VERSION,
(SQLPOINTER)SQL_OV_ODBC3,SQL_IS_INTEGER );
}
// 파괴자:연결 핸들을 해제한다.
CQuery::~CQuery()
{
if (hStmt)
SQLFreeHandle(SQL_HANDLE_STMT,hStmt);
if (hDbc)
SQLDisconnect(hDbc);
if (hDbc)
SQLFreeHandle(SQL_HANDLE_DBC,hDbc);
if (hEnv)
SQLFreeHandle(SQL_HANDLE_ENV,hEnv);
}
// 연결 핸들을 할당하고 연결한 후 명령핸들까지 같이 할당한다.
// Type = 1 : ConStr은 MDB 파일의 경로를 가진다.
경로 생략시 현재 디렉토리에서 MDB를 찾는다.
// Type = 2 : ConStr은 SQL 서버의 연결 정보를 가지는 DSN 파일의 경로를 가진다.
// 경로는 반드시 완전 경로로 지정해야 한다.
// Type = 3 : SQLConnect 함수로 DSN에 직접 연결한다.
// 연결 또는 명령 핸들 할당에 실패하면 FALSE를 리턴한다.
BOOL CQuery::Connect(int Type, TCHAR *ConStr, TCHAR *UID, TCHAR *PWD)
{
SQLCHAR InCon[255];
SQLCHAR OutCon[255];
SQLSMALLINT cbOutCon;
int ii=1;
SQLRETURN Ret;
SQLINTEGER NativeError;
SQLCHAR SqlState[6], Msg[255];
SQLSMALLINT MsgLen;
TCHAR str[256];
// 연결 타입에 따라 MDB 또는 SQL 서버, 또는 DSN에 연결한다.
SQLAllocHandle(SQL_HANDLE_DBC,hEnv,&hDbc);
switch (Type)
{
case 1:
wsprintf((TCHAR *)InCon,"DRIVER={Microsoft Access Driver (*.mdb)};DBQ=
%s;",ConStr);
ret = SQLDriverConnect(hDbc,NULL,(SQLCHAR *)InCon,sizeof(InCon),OutCon,
sizeof(OutCon),&cbOutCon, SQL_DRIVER_NOPROMPT);
break;
case 2:
wsprintf((TCHAR *)InCon, "FileDsn=%s",ConStr);
ret = SQLDriverConnect(hDbc,NULL,(SQLCHAR *)InCon,sizeof(InCon),OutCon,
sizeof(OutCon),&cbOutCon, SQL_DRIVER_NOPROMPT);
break;
case 3:
ret = SQLConnect( hDbc,(SQLCHAR *)ConStr,SQL_NTS,
(SQLCHAR *)UID,SQL_NTS,
(SQLCHAR *)PWD,SQL_NTS );
break;
}
// 접속 에러시 진단 정보를 보여준다.
if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO))
{
while (Ret=SQLGetDiagRec(SQL_HANDLE_DBC, hDbc, ii, SqlState, &NativeError,
Msg, sizeof(Msg), &MsgLen)!=SQL_NO_DATA)
{
wsprintf(str, "SQLSTATE:%s, 진단정보:%s",(LPCTSTR)SqlState,
(LPCTSTR)Msg);
::MessageBox(NULL,str,"진단 정보",0);
ii++;
}
return FALSE;
}
// 명령 핸들을 할당한다.
ret=SQLAllocHandle(SQL_HANDLE_STMT,hDbc,&hStmt);
if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO))
{
hStmt=0;
return FALSE;
}
return TRUE;
}
// SQL문을 실행한다. 실패시 진단 정보를 출력하고 FALSE를 리턴한다.
BOOL CQuery::Exec(LPCTSTR szSQL)
{
int c;
// SQL문을 실행한다. SQL_NO_DATA를 리턴한 경우도 일단 성공으로 취급한다.
// 이 경우 Fetch에서 EOF를 리턴하도록 되어 있기 때문에 진단 정보를 출력할 필요는 없다.
ret = SQLExecDirect(hStmt,(SQLCHAR *)szSQL,SQL_NTS);
if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO) &&
(ret != SQL_NO_DATA))
{
PrintDiag();
return FALSE;
}
// Update, Delete, Insert 명령시 영향받은 레코드 개수를 구해 놓는다.
SQLRowCount(hStmt,&AffectCount);
SQLNumResultCols(hStmt,&nCol);
if (nCol > MAXCOL)
{
::MessageBox(NULL,"최대 컬럼 수를 초과했습니다","CQuery 에러",MB_OK);
return FALSE;
}
// nCol이 0인 경우는 Select문 이외의 다른 명령을 실행한 경우이므로
// 바인딩을 할 필요가 없다.
if (nCol == 0)
{
Clear();
return TRUE;
}
// 모든 컬럼을 문자열로 바인딩해 놓는다. Col배열은 zero base,
// 컬럼 번호는 one base임에 유의할 것
for (c=0; c<nCol; c++)
{
SQLBindCol(hStmt,c+1,SQL_C_CHAR,Col[c],255,&lCol[c]);
SQLDescribeCol(hStmt,c+1,ColName[c],30,NULL,NULL,NULL,NULL,NULL);
}
return TRUE;
}
// SQL문을 실행하고 결과셋의 첫 칼럼에서 정수값을 읽어 리턴해 준다.
// 결과를 얻은 후 정리까지 해 준다.
int CQuery::ExecGetInt(LPCTSTR szSQL)
{
int i;
if (Exec(szSQL) == FALSE)
return CQUERYERROR;
// 데이터가 없는 경우 EOF를 리턴한다.
if (Fetch()==SQL_NO_DATA)
{
Clear();
return CQUERYEOF;
}
i = GetInt(1);
Clear();
return i;
}
// SQL문을 실행하고 결과셋의 첫 칼럼에서 문자열을 읽어 리턴해 준다.
void CQuery::ExecGetStr(LPCTSTR szSQL, TCHAR *buf)
{
// SQL문 실행중 에러가 발생한 경우 문자열 버퍼에 에러를 돌려 보낸다.
if (Exec(szSQL) == FALSE)
{
lstrcpy(buf, "CQUERYERROR");
return;
}
// 데이터가 없는 경우 EOF를 리턴한다.
if (Fetch()==SQL_NO_DATA)
{
Clear();
lstrcpy(buf,"EOF");
return;
}
GetStr(1,buf);
Clear();
}
// 결과셋에서 한 행을 가져온다.
SQLRETURN CQuery::Fetch()
{
ret = SQLFetch(hStmt);
return ret;
}
// 커서를 닫고 바인딩 정보를 해제한다.
void CQuery::Clear()
{
SQLCloseCursor(hStmt);
SQLFreeStmt(hStmt, SQL_UNBIND);
}
// 컬럼 이름으로부터 컬럼 인덱스를 찾는다. 없을 경우 -1을 리턴한다.
int CQuery::FindCol(TCHAR *name)
{
int i;
for (i=0;i<nCol;i++)
{
if (stricmp(name,(LPCTSTR)ColName[i])==0)
return i+1;
}
return -1;
}
// nCol의 컬럼값을 정수로 읽어준다. NULL일 경우 CQUERYNULL을 리턴한다.
int CQuery::GetInt(int nCol)
{
if (nCol > this->nCol)
return CQUERYNOCOL;
if (lCol[nCol-1] == SQL_NULL_DATA)
{
return CQUERYNULL;
}
else
{
return atoi(Col[nCol-1]);
}
}
// sCol의 컬럼값을 정수로 읽어준다.
int CQuery::GetInt(TCHAR *sCol)
{
int n;
n = FindCol(sCol);
if (n == -1)
{
return CQUERYNOCOL;
}
else
{
return GetInt(n);
}
}
// nCol의 컬럼값을 문자열로 읽어준다. NULL일 경우 문자열에 NULL을 채워준다.
// buf의 길이는 최소한 256이어야 하며 길이 점검은 하지 않는다.
void CQuery::GetStr(int nCol, TCHAR *buf)
{
if (nCol > this->nCol)
{
lstrcpy(buf, "CQUERYNOCOL");
return;
}
if (lCol[nCol-1] == SQL_NULL_DATA)
{
lstrcpy(buf,"NULL");
}
else
{
lstrcpy(buf,Col[nCol-1]);
}
}
// sCol의 컬럼값을 문자열로 읽어준다.
void CQuery::GetStr(TCHAR *sCol, TCHAR *buf)
{
int n;
n = FindCol(sCol);
if (n == -1)
{
lstrcpy(buf,"ERROR:Colume not found");
}
else
{
GetStr(n, buf);
}
}
// 에러 발생시 진단 정보를 출력해 준다.
void CQuery::PrintDiag()
{
int ii;
SQLRETURN Ret;
SQLINTEGER NativeError;
SQLCHAR SqlState[6], Msg[255];
SQLSMALLINT MsgLen;
TCHAR str[256];
ii = 1;
while (Ret=SQLGetDiagRec(SQL_HANDLE_STMT, hStmt, ii, SqlState, &NativeError,
Msg, sizeof(Msg), &MsgLen)!=SQL_NO_DATA)
{
wsprintf(str, "SQLSTATE:%s, 진단정보:%s",(LPCTSTR)SqlState,(LPCTSTR)Msg);
::MessageBox(NULL,str,"진단 정보",0);
ii++;
}
// 복구 불가능한 에러가 발생한 경우 프로그램을 종료한다. 극단적 예외 처리가 불필요한
// 경우는 이 코드를 주석 처리하거나 적당하게 다른 루틴으로 바꿔야 한다.
/* if (ii > 1)
{
::MessageBox(NULL,"진단 정보가 출력되었습니다. 이 정보가 출력될 경우는 네트웍
끊김, DB 서버 중지 등의\r\n" "복구 불가능한 에러가 발생한
경우이며 프로그램 실행을 계속할 수 없습니다.\r\n"
"에러를 수정하신 후 프로그램을 다시 실행해 주십시오."
,"Critical Error",MB_OK | MB_ICONERROR);
// 다음 둘 중 하나를 선택할 것
PostQuitMessage(0);
// ExitProcess(0);
}
*/
}
// BLOB 데이터를 buf에 채워준다. 이때 buf는 충분한 크기의 메모리를 미리 할당해 놓아야 한다.
// NULL일 경우 0을 리턴하고 에러 발생시 -1을 리턴한다. 성공시 읽은 총 바이트 수를 리턴한다.
// szSQL은 하나의 BLOB 필드를 읽는 Select SQL문이어야 한다.
int CQuery::ReadBlob(LPCTSTR szSQL, void *buf)
{
SQLCHAR BinaryPtr[BLOBBATCH];
SQLINTEGER LenBin;
char *p;
int nGet;
int TotalGet = 0;
ret = SQLExecDirect(hStmt,(SQLCHAR *)szSQL,SQL_NTS);
if (ret!=SQL_SUCCESS)
{
PrintDiag();
return -1;
}
while ((ret=SQLFetch(hStmt)) != SQL_NO_DATA)
{
p = (char *)buf;
while ((ret=SQLGetData(hStmt,1,SQL_C_BINARY,BinaryPtr,sizeof(BinaryPtr),
&LenBin))!=SQL_NO_DATA)
{
if (LenBin == SQL_NULL_DATA)
{
Clear();
return 0;
}
if (ret == SQL_SUCCESS)
nGet=LenBin;
else
nGet=BLOBBATCH;
TotalGet += nGet;
memcpy(p,BinaryPtr,nGet);
p += nGet;
}
}
Clear();
return TotalGet;
}
// buf의 BLOB 데이터를 저장한다.
// szSQL은 하나의 BLOB 데이터를 저장하는 Update, Insert SQL문이어야 한다.
void CQuery::WriteBlob(LPCTSTR szSQL, void *buf, int size)
{
SQLINTEGER cbBlob;
char tmp[BLOBBATCH],*p;
SQLPOINTER pToken;
int nPut;
cbBlob = SQL_LEN_DATA_AT_EXEC(size);
SQLBindParameter(hStmt,1,SQL_PARAM_INPUT,SQL_C_BINARY,
SQL_LONGVARBINARY,size,0,(SQLPOINTER)1,0,&cbBlob);
SQLExecDirect(hStmt,(SQLCHAR *)szSQL,SQL_NTS);
ret = SQLParamData(hStmt, &pToken);
while (ret == SQL_NEED_DATA)
{
if (ret==SQL_NEED_DATA)
{
if ((int)pToken==1)
{
for (p=(char *)buf; p<(char *)buf+size; p+=BLOBBATCH)
{
nPut = min(BLOBBATCH,(char *)buf+size-p);
memcpy(tmp,p,nPut);
SQLPutData(hStmt,(PTR)tmp,nPut);
}
}
}
ret = SQLParamData(hStmt, &pToken);
}
Clear();
}
------------------------------------------------------------------------------------
'IT_Programming > MFC · API' 카테고리의 다른 글
[API] 메모리 공유 (0) | 2009.09.07 |
---|---|
[API] 더블 버퍼링 스크롤바 테스트! (0) | 2009.08.04 |
[펌] 유니코드를 위한 Win API | MFC 프로그래밍 (0) | 2009.07.31 |
[API] 슈퍼 클래싱 (Super Classing) (0) | 2009.07.29 |
[API] 서브 클래싱( SUB CLASSING ) (0) | 2009.07.29 |