자체적인 ORM을 작성해 보기로 했었다.
이제 필요한 것은 실행 시 XML 파일과 실제 DB를 비교하고 동기화하는 것이다.
1. 클래스 작성
양이 많아서 일부만 옮긴다.
A. ConsoleLog
먼저 콘솔에 로그를 더 효율적으로 찍기 위한 클래스를 작성한다.
// ConsoleLog.h
#pragma once
enum class Color
{
BLACK,
WHITE,
RED,
GREEN,
BLUE,
YELLOW,
};
class ConsoleLog
{
enum { BUFFER_SIZE = 4096 };
public:
ConsoleLog();
~ConsoleLog();
public:
void WriteStdOut(Color color, const WCHAR* str, ...);
void WriteStdErr(Color color, const WCHAR* str, ...);
protected:
void SetColor(bool stdOut, Color color);
private:
HANDLE _stdOut;
HANDLE _stdErr;
};
// ConsoleLog.cpp
#include "pch.h"
#include "ConsoleLog.h"
ConsoleLog::ConsoleLog()
{
_stdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);
_stdErr = ::GetStdHandle(STD_ERROR_HANDLE);
}
ConsoleLog::~ConsoleLog()
{
}
void ConsoleLog::WriteStdOut(Color color, const WCHAR* format, ...)
{
if (format == nullptr)
return;
SetColor(true, color);
va_list ap;
va_start(ap, format);
::vwprintf(format, ap);
va_end(ap);
fflush(stdout);
SetColor(true, Color::WHITE);
}
void ConsoleLog::WriteStdErr(Color color, const WCHAR* format, ...)
{
WCHAR buffer[BUFFER_SIZE];
if (format == nullptr)
return;
SetColor(false, color);
va_list ap;
va_start(ap, format);
::vswprintf_s(buffer, BUFFER_SIZE, format, ap);
va_end(ap);
::fwprintf_s(stderr, buffer);
fflush(stderr);
SetColor(false, Color::WHITE);
}
void ConsoleLog::SetColor(bool stdOut, Color color)
{
static WORD SColors[]
{
0,
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
FOREGROUND_RED | FOREGROUND_INTENSITY,
FOREGROUND_GREEN | FOREGROUND_INTENSITY,
FOREGROUND_BLUE | FOREGROUND_INTENSITY,
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY
};
::SetConsoleTextAttribute(stdOut ? _stdOut : _stdErr, SColors[static_cast<int32>(color)]);
}
stdout
및 stderr
로 로그를 찍는 것을 간단히 했고, 색을 지정할 수 있도록 했다.
B. DBModel
// DBModel.h
#pragma once
#include "Types.h"
NAMESPACE_BEGIN(DBModel)
USING_SHARED_PTR(Column);
USING_SHARED_PTR(Index);
USING_SHARED_PTR(Table);
USING_SHARED_PTR(Procedure);
// 실제 사용되는 SQL Server 기준 오브젝트 타입 넘버
enum class DataType
{
None = 0,
TinyInt = 48,
SmallInt = 52,
Int = 56,
Real = 59,
DateTime = 61,
Float = 62,
Bit = 104,
Numeric = 108,
BigInt = 127,
VarBinary = 165,
Varchar = 167,
Binary = 173,
NVarChar = 231,
};
class Column
{
public:
String CreateText();
public:
String _name;
int32 _columnId = 0; // DB
DataType _type = DataType::None;
String _typeText;
int32 _maxLength = 0;
bool _nullable = false;
bool _identity = false;
int64 _seedValue = 0;
int64 _incrementValue = 0;
String _default;
String _defaultConstraintName; // DB
};
enum class IndexType
{
Clustered = 1,
NonClustered = 2
};
class Index
{
public:
String GetUniqueName();
String CreateName(const String& tableName);
String GetTypeText();
String GetKeyText();
String CreateColumnsText();
bool DependsOn(const String& columnName);
public:
String _name; // DB
int32 _indexId = 0; // DB
IndexType _type = IndexType::NonClustered;
bool _primaryKey = false;
bool _uniqueConstraint = false;
Vector<ColumnRef> _columns;
};
class Table
class Procedure
class Helpers
NAMESPACE_END
DBModel
클래스는 이름에서 유추할 수 있듯이 DB의 구조를 정의하는 클래스이다.
DB의 데이터를 비교하는데 사용될 것이다.
C. DBSynchronizer
// DBSynchronizer.h
#pragma once
#include "DBConnection.h"
#include "DBModel.h"
class DBSynchronizer
{
enum
{
PROCEDURE_MAX_LEN = 10000
};
enum UpdateStep : uint8
{
DropIndex,
AlterColumn,
AddColumn,
CreateTable,
DefaultConstraint,
CreateIndex,
DropColumn,
DropTable,
StoredProcecure,
Max
};
enum ColumnFlag : uint8
{
Type = 1 << 0,
Nullable = 1 << 1,
Identity = 1 << 2,
Default = 1 << 3,
Length = 1 << 4,
};
public:
DBSynchronizer(DBConnection& conn) : _dbConn(conn) { }
~DBSynchronizer();
bool Synchronize(const WCHAR* path);
private:
void ParseXmlDB(const WCHAR* path);
bool GatherDBTables();
bool GatherDBIndexes();
bool GatherDBStoredProcedures();
void CompareDBModel();
void CompareTables(DBModel::TableRef dbTable, DBModel::TableRef xmlTable);
void CompareColumns(DBModel::TableRef dbTable, DBModel::ColumnRef dbColumn, DBModel::ColumnRef xmlColumn);
void CompareStoredProcedures();
void ExecuteUpdateQueries();
private:
DBConnection& _dbConn;
Vector<DBModel::TableRef> _xmlTables;
Vector<DBModel::ProcedureRef> _xmlProcedures;
Set<String> _xmlRemovedTables;
Vector<DBModel::TableRef> _dbTables;
Vector<DBModel::ProcedureRef> _dbProcedures;
private:
Set<String> _dependentIndexes;
Vector<String> _updateQueries[UpdateStep::Max];
};
// DBSynchronizer.cpp
// ...
bool DBSynchronizer::Synchronize(const WCHAR* path)
{
ParseXmlDB(path);
GatherDBTables();
GatherDBIndexes();
GatherDBStoredProcedures();
CompareDBModel();
ExecuteUpdateQueries();
return true;
}
// ...
void DBSynchronizer::ExecuteUpdateQueries()
{
for (int32 step = 0; step < UpdateStep::Max; step++)
{
for (String& query : _updateQueries[step])
{
_dbConn.Unbind();
ASSERT_CRASH(_dbConn.Execute(query.c_str()));
}
}
}
// ...
실제로 DB 동기화를 수행하기 위한 클래스이다.
Synchronize()
함수만이 public
으로 열려있다. 다른 함수들은 동기화를 위한 조각들일뿐이다.
- XML을 파싱 한다.
- DB에 있는 테이블 등의 데이터를 가져온다.
- XML과 DB의 내용을 비교한다.
- 비교하여 DB를 업데이트한다.
크게 보면 이런 순서이다.
특히 step
이라는 것에 주목할 수 있다.
ExecuteUpdateQueries()
를 보면 모아운 쿼리를 일괄적으로 실행함을 볼 수 있다.
이는 수정사항을 그때그때 실행하는 것이 아니라 모아뒀다가 실행하기 때문이다.
코드 중간중간에 아래와 같이 쿼리를 밀어 넣는 부분이 있다.
_updateQueries[UpdateStep::CreateIndex].push_back(DBModel::Helpers::Format(
L"ALTER TABLE [dbo].[%s] ADD CONSTRAINT [%s] %s %s (%s)",
xmlTable->_name.c_str(),
xmlIndex->CreateName(xmlTable->_name).c_str(),
xmlIndex->GetKeyText().c_str(),
xmlIndex->GetTypeText().c_str(),
xmlIndex->CreateColumnsText().c_str()));
이렇게 모든 쿼리문을 차곡차곡 모아둔다.
하지만 이를 무분별하게 실행하는 것은 아니다.
만약 Dependency
가 있다면 어떤 작업에 따라 영향을 주고받을 수도 있다.
따라서 UpdateStep
이라는 enum
에 정의된 우선순위에 따라 실행된다.
대단히 중요히 지켜야 하는 순서는 아니지만 문제가 될 수 있는 부분을 예방할 수 있다면 좋을 것이다.
2. 동작 확인
기존에 있던 DB를 밀어버리고 테스트해 보자.
로그에 찍힌 대로 DB가 잘 구성됐는지 확인한다.
제대로 테이블이 구성됐다.
그럼 Gold
테이블에 새로운 Column
을 추가해 보자.
<Column name="job" type="int" notnull="false" />
위와 같은 Column
을 추가했다.
과연 반영이 될까?
제대로 수정사항이 반영됨을 확인할 수 있다.
3. 하지만...
갑자기 엄청난 양의 코드가 들어와 제대로 코드를 분석해 볼 시간이 부족했다.
전체적인 흐름은 알았지만 세부적인 동작까지 확인을 하지 못했으니 제대로 공부하지 못한 것과 같다.
일단 강의를 마무리 짓고 부족했던 부분을 돌아볼 수 있으면 좋을 것 같다.
'Study > C++ & C#' 카테고리의 다른 글
[C#] 자동화 플러그인 수정 (0) | 2023.10.17 |
---|---|
[C++ / Python / DB] ProcedureGenerator (0) | 2023.09.01 |
[C++ / DB] XML Parser (0) | 2023.08.31 |
[C++ / DB] DBBind (0) | 2023.08.29 |
[C++ / DB] DBConnection (0) | 2023.08.29 |