선 정리

bind를 더 편하게 하기 위한 클래스 DBBind를 만들어본다.


이전 버전의 한가지 아쉬운점은

dbConn->BineParem(1, SQL_C_LONG, SQL_INTEGER, sizeof(gold), &gold, &len)

이렇게 타입을 일일히 지정해줘야 하는데다 파라매터가 6개나 들어가기 때문에 실수할 여지가 많다.

이를 해결하기 위해 자주 쓰는 타입들에 대한 오버로딩을 만들도록 하자.

class DBConnection
{
/*...*/
public:
	bool        BindParam(int32 paramIndex, bool* value, SQLLEN* index);
	bool        BindParam(int32 paramIndex, float* value, SQLLEN* index);
	bool        BindParam(int32 paramIndex, double* value, SQLLEN* index);
	bool        BindParam(int32 paramIndex, int8* value, SQLLEN* index);
	bool        BindParam(int32 paramIndex, int16* value, SQLLEN* index);
	bool        BindParam(int32 paramIndex, int32* value, SQLLEN* index);
	bool        BindParam(int32 paramIndex, int64* value, SQLLEN* index);
	bool        BindParam(int32 paramIndex, TIMESTAMP_STRUCT* value, SQLLEN* index);
	bool        BindParam(int32 paramIndex, const WCHAR* str, SQLLEN* index);
	bool        BindParam(int32 paramIndex, const BYTE* bin, int32 size, SQLLEN* index);

	bool        BindCol(int32 columnIndex, bool* value, SQLLEN* index);
	bool        BindCol(int32 columnIndex, float* value, SQLLEN* index);
	bool        BindCol(int32 columnIndex, double* value, SQLLEN* index);
	bool        BindCol(int32 columnIndex, int8* value, SQLLEN* index);
	bool        BindCol(int32 columnIndex, int16* value, SQLLEN* index);
	bool        BindCol(int32 columnIndex, int32* value, SQLLEN* index);
	bool        BindCol(int32 columnIndex, int64* value, SQLLEN* index);
	bool        BindCol(int32 columnIndex, TIMESTAMP_STRUCT* value, SQLLEN* index);
	bool        BindCol(int32 columnIndex, WCHAR* str, int32 size, SQLLEN* index);
	bool        BindCol(int32 columnIndex, BYTE* bin, int32 size, SQLLEN* index);

private: // 이 부분이 public에서 private로 바뀜. 외부에서는 사용하지 않기 때문
    bool        BineParem(SQLUSMALLINT paramIndex, SQLSMALLINT cType, SQLSMALLINT sqlType, SQLULEN len, SQLPOINTER ptr, SQLLEN* index);
/*...*/
};

bool DBConnection::BindParam(int32 paramIndex, bool* value, SQLLEN* index)
{
	return BindParam(paramIndex, SQL_C_TINYINT, SQL_TINYINT, size32(bool), value, index);
}

/*...*/

bool DBConnection::BindParam(int32 paramIndex, const WCHAR* str, SQLLEN* index)
{
	SQLULEN size = static_cast<SQLULEN>((::wcslen(str) + 1) * 2);
	*index = SQL_NTSL;

	if (size > WVARCHAR_MAX)
		return BindParam(paramIndex, SQL_C_WCHAR, SQL_WLONGVARCHAR, size, (SQLPOINTER)str, index);
	else
		return BindParam(paramIndex, SQL_C_WCHAR, SQL_WVARCHAR, size, (SQLPOINTER)str, index);
}

bool DBConnection::BindParam(int32 paramIndex, const BYTE* bin, int32 size, SQLLEN* index)
{
	if (bin == nullptr)
	{
		*index = SQL_NULL_DATA;
		size = 1;
	}
	else
		*index = size;

	if (size > BINARY_MAX)
		return BindParam(paramIndex, SQL_C_BINARY, SQL_LONGVARBINARY, size, (BYTE*)bin, index);
	else
		return BindParam(paramIndex, SQL_C_BINARY, SQL_BINARY, size, (BYTE*)bin, index);
}

bool DBConnection::BindCol(int32 columnIndex, bool* value, SQLLEN* index)
{
	return BindCol(columnIndex, SQL_C_TINYINT, size32(bool), value, index);
}

/*...*/
bool DBConnection::BindCol(int32 columnIndex, WCHAR* str, int32 size, SQLLEN* index)
{
	return BindCol(columnIndex, SQL_C_WCHAR, size, str, index);
}

bool DBConnection::BindCol(int32 columnIndex, BYTE* bin, int32 size, SQLLEN* index)
{
	return BindCol(columnIndex, SQL_BINARY, size, bin, index);
}

WCHAR*와 BYTE* 은 BindParam에서 좀 다르다.

 

이제 main에서 BindParam이나 BindCol을 사용할 때 좀 더 간편하다. type과 type길이를 안써줘도 된다!

/*...*/
int32 gold = 100;
SQLLEN len = 0;

WCHAR name[100] = L"도돈탁";
SQLLEN nameLen = 0;

TIMESTAMP_STRUCT ts = { 2025, 9, 5 };
SQLLEN tsLen = 0;

ASSERT_CRASH(dbConn->BindParam(1, &gold, &len));
ASSERT_CRASH(dbConn->BindParam(2, name, &nameLen));
ASSERT_CRASH(dbConn->BindParam(3, &ts, &tsLen));
/*...*/

 


아직 아쉬운점이 남아있는데

SQLLEN을 귀찮게 직접 만들어서 넣어준다는 것.

그리고 param 갯수와 column 갯수를 쿼리문에 맞춰서 실행해야만 한다는 점. 갯수가 안맞으면 크래시가 나겠지만 나중엔 execute가 여러가지 사유로 실패할 수 있는데 그걸 매번 로그를 보고 해결하기는 귀찮은 일이다. 그래서, 진짜로 모든 데이터들을 순서에 맞게 매핑을 했는지 궁금한 것. 그리고 갯수와 종류는 맞췄는데 실수로 순서를 바꿨다던지 할수도 있다.

 

그래서 이런 실수의 여지를 줄이기 위해서 DBBind 클래스를 만들어보자. bind를 도와주는 클래스.

설명은 하단에.

#pragma once
#include "DBConnection.h"

template<int32 C>
struct FullBits { enum { value = (1 << (C - 1)) | FullBits<C - 1>::value }; };

template<>
struct FullBits<1> { enum { value = 1 }; };

template<>
struct FullBits<0> { enum { value = 0 }; };

template<int32 ParamCount, int32 ColumnCount>
class DBBind
{
public:
	DBBind(DBConnection& dbConnection, const WCHAR* query)
		: _dbConnection(dbConnection), _query(query)
	{
		::memset(_paramIndex, 0, sizeof(_paramIndex));
		::memset(_columnIndex, 0, sizeof(_columnIndex));
		_paramFlag = 0;
		_columnFlag = 0;
		dbConnection.Unbind();
	}

	bool Validate()
	{
		return _paramFlag == FullBits<ParamCount>::value && _columnFlag == FullBits<ColumnCount>::value;
	}

	bool Execute()
	{
		ASSERT_CRASH(Validate());
		return _dbConnection.Execute(_query);
	}

	bool Fetch()
	{
		return _dbConnection.Fetch();
	}

public:
	template<typename T>
	void BindParam(int32 idx, T& value)
	{
		_dbConnection.BindParam(idx + 1, &value, &_paramIndex[idx]);
		_paramFlag |= (1LL << idx);
	}

	void BindParam(int32 idx, const WCHAR* value)
	{
		_dbConnection.BindParam(idx + 1, value, &_paramIndex[idx]);
		_paramFlag |= (1LL << idx);
	}

	template<typename T, int32 N>
	void BindParam(int32 idx, T(&value)[N])
	{
		_dbConnection.BindParam(idx + 1, (const BYTE*)value, size32(T) * N, &_paramIndex[idx]);
		_paramFlag |= (1LL << idx);
	}

	template<typename T>
	void BindParam(int32 idx, T* value, int32 N)
	{
		_dbConnection.BindParam(idx + 1, (const BYTE*)value, size32(T) * N, &_paramIndex[idx]);
		_paramFlag |= (1LL << idx);
	}

	template<typename T>
	void BindCol(int32 idx, T& value)
	{
		_dbConnection.BindCol(idx + 1, &value, &_columnIndex[idx]);
		_columnFlag |= (1LL << idx);
	}

	template<int32 N>
	void BindCol(int32 idx, WCHAR(&value)[N])
	{
		_dbConnection.BindCol(idx + 1, value, N - 1, &_columnIndex[idx]);
		_columnFlag |= (1LL << idx);
	}

	void BindCol(int32 idx, WCHAR* value, int32 len)
	{
		_dbConnection.BindCol(idx + 1, value, len - 1, &_columnIndex[idx]);
		_columnFlag |= (1LL << idx);
	}

	template<typename T, int32 N>
	void BindCol(int32 idx, T(&value)[N])
	{
		_dbConnection.BindCol(idx + 1, value, size32(T) * N, &_columnIndex[idx]);
		_columnFlag |= (1LL << idx);
	}

protected:
	DBConnection& _dbConnection;
	const WCHAR* _query;
	SQLLEN			_paramIndex[ParamCount > 0 ? ParamCount : 1];
	SQLLEN			_columnIndex[ColumnCount > 0 ? ColumnCount : 1];
	uint64			_paramFlag;
	uint64			_columnFlag;
};

 

SQLLEN을 하나하나 들고있었는데, 편하게 쓰기위해 정적 배열을 만들어준다.

그리고 모든 인자들을 bind 마쳤는지 확인하기 위한 BitFlag를 만든다.

DBBind<1,4> dbBind("쿼리문"); 이렇게 생성하면 param은 1개, column은 4개인 쿼리문을 만들겠다는 뜻.

맨처음 FullBits는 갯수가 맞는지, 겹침이나 누락이 없는지 확인하는 용도. (크기가 같으면서 순서가 뒤바뀌는건 대응 안될 듯)

 

 

 

'강의 수강 > 게임서버(1)' 카테고리의 다른 글

79. ORM  (0) 2025.09.10
78. XML Parser  (0) 2025.09.06
76. DB Connection  (0) 2025.09.01
75. JobTimer  (0) 2025.08.25
74. JobQueue #5  (0) 2025.08.25

+ Recent posts