[C++ / DB] ORM

2023. 9. 1. 03:16·Study/C++ & C#

자체적인 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으로 열려있다. 다른 함수들은 동기화를 위한 조각들일뿐이다.

 

  1. XML을 파싱 한다.
  2. DB에 있는 테이블 등의 데이터를 가져온다.
  3. XML과 DB의 내용을 비교한다.
  4. 비교하여 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
'Study/C++ & C#' 카테고리의 다른 글
  • [C#] 자동화 플러그인 수정
  • [C++ / Python / DB] ProcedureGenerator
  • [C++ / DB] XML Parser
  • [C++ / DB] DBBind
BVM
BVM
  • BVM
    E:\
    BVM
  • 전체
    오늘
    어제
    • 분류 전체보기 (168)
      • Thoughts (14)
      • Study (69)
        • Japanese (3)
        • C++ & C# (46)
        • Javascript (3)
        • Python (14)
        • Others (3)
      • Play (1)
        • Battlefield (1)
      • Others (11)
      • Camp (73)
        • T.I.L. (57)
        • Temp (1)
        • Standard (10)
        • Challenge (3)
        • Project (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • 본 블로그 개설의 목적
  • 인기 글

  • 태그

    Network
    네트워크 프로그래밍
    IOCP
    boost
    Dalamud
    discord py
    Selenium
    클라우드
    암호화
    7계층
    JS
    c#
    C++
    서버
    bot
    네트워크
    docker
    db
    Asio
    베데스다
    FF14
    Server
    OSI
    포인터
    discord
    로깅
    Python
    프로그래머스
    스타필드
    cloudtype
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
BVM
[C++ / DB] ORM
상단으로

티스토리툴바