KoreanFoodie's Study
[C++ 게임 서버] 7-5. Procedure Generator 본문
[C++ 게임 서버] 7-5. Procedure Generator
핵심 :
1. Python 을 이용해 Procedures 를 자동 생성해 보자.
우리는 이전에 BindParam/BindCol 을 이용해서 테이블에 접근을 하곤 했다.
이런 부분을 조금 더, 자동화 시키기 위해 ProcedureGenerator 를 만들어 보자. 우리는 XML 로부터, 테이블에 데이터를 추가하거나 조회하는 API 를 자동으로 만들어 줄 것이다.
먼저 XmlDBParser 가 필요하다. 이건 참고용이니, 접은 글에 넣도록 하겠다. 🙂
더보기
XmlDBParser.py
import xml.etree.ElementTree as ET
class XmlDBParser:
def __init__(self):
self.tables = {}
self.procedures = []
def parse_xml(self, path):
tree = ET.parse(path)
root = tree.getroot()
for child in root:
if child.tag == 'Table':
self.tables[child.attrib['name']] = Table(child)
for child in root:
if child.tag == 'Procedure':
self.procedures.append(Procedure(child, self.tables))
class Table:
def __init__(self, node):
self.name = node.attrib['name']
self.columns = {}
for child in node:
if child.tag == 'Column':
self.columns[child.attrib['name']] = ReplaceType(child.attrib['type'])
class Procedure:
def __init__(self, node, tables):
name = node.attrib['name']
if name.startswith('sp'):
self.name = name[2:]
else:
self.name = name
self.params = []
for child in node:
if child.tag == 'Param':
self.params.append(Param(child))
elif child.tag == 'Body':
self.columns = ParseColumns(child, tables)
self.questions = MakeQuestions(self.params)
class Param:
def __init__(self, node):
name = node.attrib['name'].replace('@', '')
self.name = name[0].upper() + name[1:]
self.type = ReplaceType(node.attrib['type'])
class Column:
def __init__(self, name, type):
self.name = name[0].upper() + name[1:]
self.type = type
def ParseColumns(node, tables):
columns = []
query = node.text
select_idx = max(query.rfind('SELECT'), query.rfind('select'))
from_idx = max(query.rfind('FROM'), query.rfind('from'))
if select_idx > 0 and from_idx > 0 and from_idx > select_idx:
table_name = query[from_idx+len('FROM') : -1].strip().split()[0]
table_name = table_name.replace('[', '').replace(']', '').replace('dbo.', '')
table = tables.get(table_name)
words = query[select_idx+len('SELECT') : from_idx].strip().split(",")
for word in words:
column_name = word.strip().split()[0]
columns.append(Column(column_name, table.columns[column_name]))
elif select_idx > 0:
word = query[select_idx+len('SELECT') : -1].strip().split()[0]
if word.startswith('@@ROWCOUNT') or word.startswith('@@rowcount'):
columns.append(Column('RowCount', 'int64'))
elif word.startswith('@@IDENTITY') or word.startswith('@@identity'):
columns.append(Column('Identity', 'int64'))
return columns
def MakeQuestions(params):
questions = ''
if len(params) != 0:
questions = '('
for idx, item in enumerate(params):
questions += '?'
if idx != (len(params)-1):
questions += ','
questions += ')'
return questions
def ReplaceType(type):
if type == 'bool':
return 'bool'
if type == 'int':
return 'int32'
if type == 'bigint':
return 'int64'
if type == 'datetime':
return 'TIMESTAMP_STRUCT'
if type.startswith('nvarchar'):
return 'nvarchar'
return type
이제 ProcedureGenerator.py 를 만들어 보자.
import argparse
import jinja2
import XmlDBParser
def main():
arg_parser = argparse.ArgumentParser(description = 'StoredProcedure Generator')
arg_parser.add_argument('--path', type=str, default='C:/Rookiss/CPP_Server/Server/GameServer/GameDB.xml', help='Xml Path')
arg_parser.add_argument('--output', type=str, default='GenProcedures.h', help='Output File')
args = arg_parser.parse_args()
if args.path == None or args.output == None:
print('[Error] --path --output required')
return
parser = XmlDBParser.XmlDBParser()
parser.parse_xml(args.path)
file_loader = jinja2.FileSystemLoader('Templates')
env = jinja2.Environment(loader=file_loader)
template = env.get_template('GenProcedures.h')
output = template.render(procs=parser.procedures)
f = open(args.output, 'w+')
f.write(output)
f.close()
print(output)
if __name__ == '__main__':
main()
참고로 위 코드는, 아래의 GenProcedures.h 를 템플릿으로 사용할 것이다.
#pragma once
#include "Types.h"
#include <windows.h>
#include "DBBind.h"
{%- macro gen_procedures() -%} {% include 'Procedure.h' %} {% endmacro %}
namespace SP
{
{{gen_procedures() | indent}}
};
그럼 아래와 같이 Procedures 코드가 생성된다 :
#pragma once
#include "Types.h"
#include <windows.h>
#include "DBBind.h"
namespace SP
{
class InsertGold : public DBBind<3,0>
{
public:
InsertGold(DBConnection& conn) : DBBind(conn, L"{CALL dbo.spInsertGold(?,?,?)}") { }
void In_Gold(int32& v) { BindParam(0, v); };
void In_Gold(int32&& v) { _gold = std::move(v); BindParam(0, _gold); };
template<int32 N> void In_Name(WCHAR(&v)[N]) { BindParam(1, v); };
template<int32 N> void In_Name(const WCHAR(&v)[N]) { BindParam(1, v); };
void In_Name(WCHAR* v, int32 count) { BindParam(1, v, count); };
void In_Name(const WCHAR* v, int32 count) { BindParam(1, v, count); };
void In_CreateDate(TIMESTAMP_STRUCT& v) { BindParam(2, v); };
void In_CreateDate(TIMESTAMP_STRUCT&& v) { _createDate = std::move(v); BindParam(2, _createDate); };
private:
int32 _gold = {};
TIMESTAMP_STRUCT _createDate = {};
};
class GetGold : public DBBind<1,4>
{
public:
GetGold(DBConnection& conn) : DBBind(conn, L"{CALL dbo.spGetGold(?)}") { }
void In_Gold(int32& v) { BindParam(0, v); };
void In_Gold(int32&& v) { _gold = std::move(v); BindParam(0, _gold); };
void Out_Id(OUT int32& v) { BindCol(0, v); };
void Out_Gold(OUT int32& v) { BindCol(1, v); };
template<int32 N> void Out_Name(OUT WCHAR(&v)[N]) { BindCol(2, v); };
void Out_CreateDate(OUT TIMESTAMP_STRUCT& v) { BindCol(3, v); };
private:
int32 _gold = {};
};
};
그럼 이제 게임 서버에서는 아래와 같이, 간편하게 생성된 Procedure 을 호출할 수 있게 된다! 😮
WCHAR name[] = L"Rookiss";
SP::InsertGold insertGold(*dbConn);
insertGold.In_Gold(100);
insertGold.In_Name(name);
insertGold.In_CreateDate(TIMESTAMP_STRUCT{ 2020, 6, 8 });
insertGold.Execute();
////////////////////////////////////////////////////////////////////////////////
SP::GetGold getGold(*dbConn);
getGold.In_Gold(100);
int32 id = 0;
int32 gold = 0;
WCHAR name[100];
TIMESTAMP_STRUCT date;
getGold.Out_Id(OUT id);
getGold.Out_Gold(OUT gold);
getGold.Out_Name(OUT name);
getGold.Out_CreateDate(OUT date);
getGold.Execute();
while (getGold.Fetch())
{
GConsoleLogger->WriteStdOut(Color::BLUE,
L"ID[%d] Gold[%d] Name[%s]\n", id, gold, name);
}
DB 연동 부분은 뭔가 큰 설명 없이, 참고용 코드만 나열하는 식으로 마무리해서 약간 아쉽긴 하다.
만약 자세한 세팅과 원리가 궁금하다면, 꼭 강의를 들어 보도록 하자. 후회하지 않을 것이다! 😉
'Game Dev > Game Server' 카테고리의 다른 글
[C++ 게임 서버] 7-4. ORM (0) | 2023.12.22 |
---|---|
[C++ 게임 서버] 7-3. XML Parser (0) | 2023.12.21 |
[C++ 게임 서버] 7-2. DB Bind (0) | 2023.12.21 |
[C++ 게임 서버] 7-1. DB Connection (0) | 2023.12.21 |
[C++ 게임 서버] 6-7. JobTimer (0) | 2023.12.21 |
Comments