MySQL 에 직접 요청 보내기

아이펀 엔진의 ORM 기능은 오브젝트로 정의한 데이터를 위한 DB 처리를 자동화 하지만, 경우에 따라서는 다른 시스템과 공유하는 데이터나 메타 데이터와 같이 오브젝트로 정의하지 않은 DB 데이터에 직접 접근해야 하는 경우가 있습니다.

Note

게임 오브젝트를 처리하기 위한 목적이라면 ORM 을 참고하세요.

이런 경우, 여러분이 직접 공개된 여러 MySQL Client 라이브러리 중 하나를 사용해서 DB 에 접근할 수도 있지만 아이펀 엔진에서 제공하는 클래스를 사용하면 병렬 처리를 위한 쓰레드 관리와 같은 번거로운 일들을 신경쓰지 않고 편리하게 사용할 수 있습니다.

Warning

MySQL Connector 로 ORM 기능으로 정의한 오브젝트 데이터를 다루면 안됩니다.

ORM 기능이 관리하는 오브젝트를 MySQL Connector 로 접근하는 것은 데이터의 유실 및 오동작의 원인이 될 수 있어 매우 위험합니다. 관련해서는 게임 서버 외부에서 DB 접근시 유의 사항 를 참조하시기 바랍니다.

Tip

아래 설명들은 모든 기능을 설명하고 있지는 않으며 자세한 내용은 MariaDB class 를 참고 하시기 바랍니다.

Mariadb 객체 생성

아이펀 엔진이 제공하는 MySQL Connector (이하 Mariadb) 기능을 사용하기 위해서는 먼저 객체를 생성해야 합니다. 생성과정에서 지정된 수만큼 DB 와 연결을 맺고 초기화 합니다.

Ptr<Mariadb> Mariadb::Create(
    const string &host,
    const string &user,
    const string &password,
    const string &schema,
    size_t num_connections
    [, const string &charset]
    [, bool auto_retry_on_deadlock]
    [, unsigned long client_flags]
    [, int64_t slow_query_logging_time_in_ms]
    [, const ConnectionFailureHandler &connection_failure_handler]);

typedef function<
    void(size_t /*total*/, size_t /*connected*/,
         const WallClock::Value &/*failure_time*/)> ConnectionFailureHandler;
Mariadb Mariadb.Create(
    host,
    user,
    password,
    schema,
    num_connections
    [, charset]
    [, auto_retry_on_deadlock]);

함수 인자 설명

  • host: 연결할 DB 서버의 아이피와 포트 정보를 포함하는 주소입니다. 예) tcp://127.0.0.1:3306

  • user: DB 계정의 ID 를 입력합니다.

  • password: DB 계정의 Password 를 입력합니다.

  • schema: DB 이름을 입력합니다.

  • num_connections: Connection Pool 의 연결 수를 입력합니다.

    Note

    연결수가 2 개 이상이면 내부적으로 연결 풀을 구성해서 병렬적으로 수행되기 때문에 쿼리의 순서가 보장되지 않습니다.

    또한 설정한 연결수가 많으면 생성단계에서 일부 연결이 실패하거나 일정 시간 후에 연결 끊김 로그가 출력될 수 있습니다.

    MySQL 서버의 설정에서 max_connections, backlog, max_allowed_packet 값을 늘리시기 바랍니다.

  • charset: Connection 의 Character Set 을 입력합니다. 생략하면 DB 기본 값을 사용합니다.

    Warning

    DB, Table, Column 를 생성할 때 설정한 charset 과 연결의 charset 이 다르면 문자열이 깨질 수 있습니다.

  • auto_retry_on_deadlock: DB 에서 데드락이 감지되면 자동으로 재시도 하도록 설정합니다. 생략하면 동작하지 않습니다. InnoDB 데드락 페이지를 참고하세요.

  • client_flags (C++ Only): MySQL 클라이언트 연결에 사용할 옵션 flag 입니다. CLIENT_MULTI_STATEMENTS, CLIENT_REMEMBER_OPTIONS 는 기본으로 설정됩니다. 자세한 설명은 mysql_real_connect 매뉴얼 를 참고하시기 바랍니다.

  • slow_query_logging_time_in_ms (C++ Only): MySQL 서버에 전송한 쿼리에 대한 지연 로그를 남길 기준값으로 밀리초 단위로 입력합니다. 기본값은 5초입니다.

  • connection_failure_handler (C++ Only): 연결이 실패하거나 연결이 끊길 때 불릴 콜백 함수를 입력합니다. 이 함수는 인자로 연결돼야할 개수와 연결된 개수를 받을 수 있습니다.

Mariadb 객체 초기화

Mariadb 객체를 생성했다면 초기화를 진행해야 사용할 수 있습니다.

void Mariadb::Initialize()
void Mariadb.Start()

객체 생성 시에 지정한 설정값에 따라 MySQL 서버와의 연결 풀을 생성하고 연결을 맺습니다. 이제 MySQL 요청을 서버로 전송할 수 있습니다.

쿼리 실행

다음의 함수들 중 하나를 이용합니다. 끝에 Sync 가 붙은 함수는 동기방식으로 동작하는 함수입니다.

typedef function<void(const Ptr<ResultSets> &/*result_sets*/,
                      const Error &/*error*/)> QueryExecuteHandler;

void Mariadb::ExecuteQuery(
                    const string &query,
                    const QueryExecuteHandler &handler);

Ptr<ResultSets> Mariadb::ExecuteQuerySync(
                    const string &query,
                    Mariadb::Error * error);
public delegate void QueryExecuteHandler(
                        ResultSets result_sets, Error error);

public void Mariadb.ExecuteQuery(
                    string query,
                    QueryExecuteHandler callback);

public ResultSets Mariadb.ExecuteQuerySync(
                    string query,
                    out Error error)

함수 인자 설명

  • query: 실행하려는 SQL Query 문자열입니다. 여러 쿼리를 포함할 수 있습니다.

  • callback: 쿼리 실행 결과를 처리하는 콜백 함수 입니다. 자세한 내용은 결과 처리 에서 설명합니다.

  • error: 오류 발생시 오류 값을 전달하는 output 변수입니다.

Note

현재 버전에서는 Prepared Statement 를 지원하지 않습니다.

결과 처리

Mariadb 객체를 통해서 전송한 SQL 쿼리의 결과는 ResultSets 객체로 반환됩니다.

ResultSets 객체는 ExecuteQuery 함수는 콜백함수의 인자로 ExecuteQuerySync 함수는 리턴값으로 전달되며 제공되는 대표적인 인터페이스에 대해서 설명하겠습니다.

Tip

ResultSets 클래스 인터페이스의 자세한 내용은 링크 를 참고해 주시기 바랍니다.

typedef function<void(const Ptr<ResultSets> &/*result_sets*/,
                      const Error &/*error*/)> QueryExecuteHandler;


// 쿼리 하나 당 하나의 ResultSet 이 생성되지만 선언 직후 ResultSet 은
// 비어있는 상태이고, 이 함수를 호출할 때마다 쿼리 순서대로 결과를
// 가져옵니다.
// 가져오기에 성공하면 true 를, 가져올 결과가 없다면 false 를
// 리턴합니다.
bool SeekNextResultSet();

// 가져온 ResultSet 에서 반환된 Row 를 순서대로 가져오기
// 위해 호출합니다.
// 이 함수를 호출할 때마다 Row 를 순서대로 가져옵니다.
// 최소 한번 이상 이 함수를 호출해야 Row 를 가져옵니다.
// 가져오기에 성공하면 true 를, 가져올 Row 가 없다면 false 를
// 리턴합니다.
bool SeekNextRow();

// 아래 함수들은 현재의 ResultSet->Row 로부터 특정 컬럼의 값을
// 읽을 때 사용합니다.
// 각 함수의 첫번째 인자는 컬럼 이름에 해당하는 문자열
// 컬럼 순서에 해당하는 0 부터 시작하는 인덱스가 될 수 있습니다.
// regard_null_as 를 지정하면 컬럼값이 NULL 일 때, 값을 지정할 수
// 있습니다.

bool GetBool(const string &column_name, bool regard_null_as = false);

int64_t GetInt(const string &column_name, int64_t regard_null_as = 0);

float GetFloat(const string &column_name, float regard_null_as = 0.0);

double GetDouble(const string &column_name, double regard_null_as = 0.0);

// 문자열 형태의 컬럼값을 읽어오기 위해 사용합니다.
// VARCHAR, JSON, VAR_STRING, STRING 타입을 읽을 수 있으며,
// GetBinary() 에서 지원하는 타입들도 읽을 수 있지만,
// GetFieldLength() 함수와 함께 사용해야 합니다.
const char * GetString(const string &column_name);

// 바이너리 형태의 컬럼값을 읽어오기 위해 사용합니다.
// TINY_BLOB, MEDIUM_BLOB, LONG_BLOB, BLOB 타입을 읽을 수 있으며,
// GetFieldLength() 함수를 사용해서 길이를 구해야 합니다.
const char * GetBinary(const string &column_name);

// 지정한 컬럼값의 길이를 반환합니다.
// 바이너리 형태의 데이터가 char * 형으로 반환될 때
// 정확한 길이를 구하기 위해 사용합니다.
uint64_t GetFieldLength(const string &colum_name);

WallClock::Value GetDateTime(const string &column_name,
    const WallClock::Value &regard_null_as = WallClock::kEpockClock);

const string GetDateTimeString(const string &column_name,
    const string &regard_null_as = "");

WallClock::Duration GetTime(size_t column_index,
    const WallClock::Duration &regard_null_as = Wallclock::kEmptyDuration);
public delegate void QueryExecuteHandler(
                        ResultSets result_sets, Error error);

// 쿼리 하나 당 하나의 ResultSet 이 생성되지만 선언 직후 ResultSet 은
// 비어있는 상태이고, 이 함수를 호출할 때마다 쿼리 순서대로 결과를
// 가져옵니다.
// 가져오기에 성공하면 true 를, 가져올 결과가 없다면 false 를
// 리턴합니다.
bool SeekNextResultSet()

// 가져온 ResultSet 에서 반환된 Row 를 순서대로 가져오기
// 위해 호출합니다.
// 이 함수를 호출할 때마다 Row 를 순서대로 가져옵니다.
// 최소 한번 이상 이 함수를 호출해야 Row 를 가져옵니다.
// 가져오기에 성공하면 true 를, 가져올 Row 가 없다면 false 를
// 리턴합니다.
bool SeekNextRow()

// 아래 함수들은 현재의 ResultSet->Row 로부터 특정 컬럼의 값을
// 읽을 때 사용합니다.
// 각 함수의 첫번째 인자는 컬럼 이름에 해당하는 문자열
// 컬럼 순서에 해당하는 0 부터 시작하는 인덱스가 될 수 있습니다.
// regard_null_as 를 지정하면 컬럼값이 NULL 일 때, 값을 지정할 수
// 있습니다.

bool GetBool(string column_name, bool regard_null_as = false)

long GetInt(string column_name, long regard_null_as = 0)

float GetFloat(string column_name, float regard_null_as = 0.0f)

double GetDouble(string column_name, double regard_null_as = 0.0)

// 문자열 형태의 컬럼값을 읽어오기 위해 사용합니다.
// VARCHAR, JSON, VAR_STRING, STRING 타입을 읽을 수 있으며,
// BLOB 같은 바이너리 타입 컬럼도 읽을 수 있지만, 데이터에 따라서는
// 정확하지 않을 수 있습니다.
string GetString(string column_name)

DateTime GetDateTime(string column_name, DateTime? regard_null_as = null)

TimeSpan GetTime(string column_name, TimeSpan? regard_null_as = null)

MySQL connector 사용 예제

예제를 통해서 Mariadb 클래스의 사용방법을 자세히 알아보겠습니다.

DB 스킴 생성

먼저, 서버 예제 코드를 작성하기 전에 DB 스킴을 생성합니다.

Member 테이블:

CREATE TABLE Member (No INT PRIMARY KEY, Name VARCHAR(32), Weight FLOAT);

INSERT INTO Member (No, Name, Weight) VALUES
(0, "dog", 12.5),
(1, "cat", 2.1),
(2, "bird", 0.5),
(3, "tiger", 290.0);

SELECT * FROM Member;
+----+-------+--------+
| No | Name  | Weight |
+----+-------+--------+
|  0 | dog   |   12.5 |
|  1 | cat   |    2.1 |
|  2 | bird  |    0.5 |
|  3 | tiger |    290 |
+----+-------+--------+

ExampleTable 테이블:

CREATE TABLE ExampleTable (date DATETIME)

BlobTable 테이블:

CREATE TABLE `BlobTable` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `data` blob,
PRIMARY KEY (`id`))

ExampleProc 프로시저:

DELIMITER $$
CREATE PROCEDURE ExampleProc ()
BEGIN
  SELECT * FROM Member;
END$$
DELIMITER ;

ExampleProc2 프로시저:

DELIMITER $$
CREATE PROCEDURE ExampleProc2 (OUT param1 INT, OUT param2 CHAR(50))
BEGIN
  SET param1 = 1234;
  SET param2 = "test";
END$$
DELIMITER ;

SELECT 예제

앞서 생성한 Member 테이블에서 데이터를 조회하는 예제입니다.

예제 코드를 실행하면 다음과 같은 결과를 출력합니다.

====================
No: 0, Name: dog, Weight: 12.5
No: 3, Name: tiger, Weight: 290
====================
No: 2, Name: bird, Weight: 0.5
Ptr<Mariadb> connections;

void MariadbConnectionFailureHandler(
                  size_t total_connection_cnt,
                  size_t connected_connection_cnt,
                  const WallClock::Value &t) {
  LOG(INFO) << "Detected Mariadb connection failure: " << t;
  LOG(INFO) << "Total connection count: " << total_connection_cnt;
  LOG(INFO) << "Current connection count: " << connected_connection_cnt;
}


void Initialize() {
  connections = Mariadb::Create(
                  "tcp://127.0.0.1:3306",
                  "myid",  // mydb 에 대해서 접근 권한이 있는 계정
                  "mypw",
                  "mydb",  // 사용할 Database 이름
                  5,       // MySQL 서버와 연결 갯수
                  "utf8",  // 연결할 때 설정할 charset.
                           // DB 스킴의 charset 과 다르면 문자가 깨질 수 있음.
                  false,   // auto_retry_on_deadlock 설정
                  CLIENT_NO_SCHEMA,      // MySQL 서버와 연결할 때 옵션.
                  1000,    // Slow 쿼리를 출력하기 위한 ms 단위 기준값.
                  // 연결에 실패했을 때, 불리는 콜백 함수.
                  MariadbConnectionFailureHandler);

  connections->Initialize();
}

void ExecuteQuery() {

  std::stringstream query;

  // 한번에 여러 쿼리를 전송할 수 있습니다.
  query << "SELECT * FROM Member WHERE Weight > 10.0;"
        << "SELECT * FROM Member WHERE Weight < 1.0;";

  connections->ExecuteQuery(query.str(), OnQueryExecuted);
}

void OnQueryExecuted(const Ptr<ResultSets> &result_sets,
                     const Mariadb::Error &error) {
  if (error.code != 0) {
    LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
    return;
  }

  // 한번 ExecuteQuery() 를 호출 할 때, SELECT 요청 만큼
  // ResultSets 를 반환합니다.
  // SeekNextResultSet(), SeekNextRow() 을 호출하지 않고,
  // results_sets 에 접근하면 크래시 할 수 있습니다.
  while (result_sets->SeekNextResultSet()) {
    LOG(INFO) << "=====================";

    // 쿼리 결과의 ROW 수 만큼 호출 해야합니다.
    while (result_sets->SeekNextRow()) {
      LOG(INFO) << "No: " << result_sets->GetInt(0)  // 또는 GetInt("No")
                << ", Name: " << result_sets->GetString(1)  // 또는 GetString("Name")
                << ", Weight: " << result_sets->GetDouble(2); // 또는 GetDouble("Weight")
    }
  }
}
Mariadb connections;

void Initialize() {
  Mariadb connections = Mariadb.Create (
      "tcp://127.0.0.1:3306",
      "myid",  // mydb 에 대해서 접근 권한이 있는 계정
      "mypw",
      "mydb",  // 사용할 Database 이름
      5,       // MySQL 서버와 연결 갯수
      "utf8",  // 연결할 때 설정할 charset.
               // DB 스킴의 charset 과 다르면 문자가 깨질 수 있음.
      false);  // auto_retry_on_deadlock 설정

  connections.Start();
}

void ExecuteQuery() {
  StringBuilder query = new StringBuilder ();
  // 한번에 여러 쿼리를 전송할 수 있습니다.
  query.Append ("SELECT * FROM Member WHERE Weight > 10.0;");
  query.Append ("SELECT * FROM Member WHERE Weight < 1.0;");

  connections.ExecuteQuery (query.ToString(), OnQueryExecuted);
}

public void OnQueryExecuted(ResultSets result_sets, Mariadb.Error error)
{
  if (error.Code != 0)
  {
    Log.Info ("Error: code={0}, desc={1}", error.Code, error.Desc);
  }

  // 한번 ExecuteQuery() 를 호출 할 때, SELECT 요청 만큼
  // ResultSets 를 반환합니다.
  // SeekNextResultSet(), SeekNextRow() 을 호출하지 않고,
  // results_sets 에 접근하면 크래시 할 수 있습니다.
  while (result_sets.SeekNextResultSet ()) {
    Log.Info ("=====================");

    // 쿼리 결과의 ROW 수 만큼 호출 해야합니다.
    while (result_sets.SeekNextRow ()) {
      Log.Info ("No: {0}, Name: {1}, Weight: {2}",
          result_sets.GetInt (0),     // 또는 GetInt("No")
          result_sets.GetString(1),   // 또는 GetString("Name")
          result_sets.GetDouble(2));  // 또는 GetDouble("Weight")
    }
  }
}

INSERT 예제

이번 예제는 앞서 생성했던 ExampleTable 테이블의 DateTime 타입 컬럼에 값을 넣어보고, SELECT 로 결과를 출력해 보겠습니다.

정상적으로 실행이 되면 다음과 같은 결과를 확인할 수 있습니다.

2016-06-14 16:23:19
Ptr<Mariadb> connections;

void Initialize() {
  // 기본적인 옵션 외에는 생략합니다.
  connections = Mariadb::Create("tcp://127.0.0.1:3306", "myid", "mypw",
                                "mydb", 5, "utf8", false);
  connections->Initialize();
}

void ExecuteQuery() {
  // MySQL DateTime 형식으로 변환합니다.
  string now = boost::posix_time::to_iso_extended_string(WallClock::Now());

  std::stringstream query;
  query << "INSERT INTO ExampleTable VALUES('" << now << "');";

  connections->ExecuteQuery(query.str(),
                  [connections](const Ptr<ResultSets> &result_sets,
                                const Mariadb::Error &error){
    if (error.code != 0) {
      LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
      return;
    }

    LOG(INFO) << "Seccessfully executed query. Next step is SELECT";

    // INSERT 요청 이후에 SELECT 요청이 실행되는 것을
    // 보장하기 위해 두번째 ExecuteQuery() 는
    // 첫번째 쿼리의 콜백 안에서 실행합니다.
    string query2 = "SELECT date FROM ExampleTable;";
    connections->ExecuteQuery(query2, OnSelected);
  });
}

void OnSelected(const Ptr<ResultSets> &result_sets,
                     const Mariadb::Error &error) {
  if (error.code != 0) {
    LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
    return;
  }

  // SELECT 쿼리를 하나만 요청했기 때문에 한번만 호출해도 됩니다.
  if (not result_sets->SeekNextResultSet()) {
    LOG(ERROR) << "no result set.";
    return;
  }

  // ROW 는 얼마나 있는지 모르기 때문에 실패할 때까지 호출합니다.
  while (result_sets->SeekNextRow()) {
    WallClock::Value datetime = result_sets->GetDateTime(0);
    LOG(INFO) << "datetime: " << datetime;
  }
}
Mariadb connections;

void Initialize() {
  Mariadb connections = Mariadb.Create (
      "tcp://127.0.0.1:3306", "funapi", "funapi", "funapi", 8, "utf8");

  connections.Start();
}

// 쿼리 실행
void ExecuteQuery()
{
  string query = "INSERT INTO ExampleTable VALUES('{0}');";
  // MySQL DateTime 형식으로 변환합니다.
  string now = WallClock.GetTimestring(WallClock.Now);

  connections.ExecuteQuery(String.Format(query, now),
      (ResultSets result_sets, Mariadb.Error error) => {
        if (error.Code != 0) {
          Log.Info ("Error: code={0}, desc={1}", error.Code, error.Desc);
        }

        Log.Info ("Successfully executed query. Next stemp is SELECT");

        string query2 = "SELECT date FROM ExampleTable;";
        // INSERT 요청 이후에 SELECT 요청이 실행되는 것을
        // 보장하기 위해 두번째 ExecuteQuery() 는
        // 첫번째 쿼리의 콜백 안에서 실행합니다.
        connections.ExecuteQuery(query2, OnQueryExecuted);
      });

  return true;
}

void OnQueryExecuted(ResultSets result_sets, Mariadb.Error error)
{
  if (error.Code != 0)
  {
    Log.Info ("Error: code={0}, desc={1}", error.Code, error.Desc);
  }

  // SELECT 쿼리를 하나만 요청했기 때문에 한번만 호출해도 됩니다.
  if (!result_sets.SeekNextResultSet ()) {
    Log.Error ("no result set.");
    return;
  }

  // ROW 는 얼마나 있는지 모르기 때문에 실패할 때까지 호출합니다.
  while (result_sets.SeekNextRow ()) {
    System.DateTime dateTime = result_sets.GetDateTime (0);
    Log.Info ("Datetime : {0}", dateTime);
  }
}

바이너리 타입 INSERT 예제

아래 예제는 Blob 등과 같은 바이너리 타입 컬럼에 값을 삽입하고, 읽어오는 예제입니다.

주의해야 하는 점은 SQL 쿼리가 문자열로 전달되기 때문에 바이너리 데이터를 16진수 문자열로 변환해야 하는 점입니다.

단, MySQL 클라이언트 연결을 통해서 한번에 읽어올 수 있는 데이터 크기에 제한을 받으므로 주의하시기 바랍니다.

Note

예제에 나온 방법대신 MySQL 클라이언트 라이브러리에서 제공하는 mysql_escate_strint() 함수를 사용해도 되지만 컬럼이 utf8 외의 인코딩을 사용하면 의도하지 않은 결과를 얻을 수 있습니다.

mysql_real_escape_string() 함수는 사용할 수 없습니다.

Ptr<Mariadb> connections;

void Initialize() {
  connections = Mariadb::Create("tcp://127.0.0.1:3306", "myid", "mypw",
                                "mydb", 5, "utf8", false);
  connections->Initialize();
}

void ExecuteQuery() {

  // 삽입할 바이너리 데이터 샘플입니다.
  char buff[16] =
      {0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x06, 0x07, 0x08, 0x09, 0x0A};

  std::stringstream query;

  // 바이너리 데이터를 MySQL 이 인식할 수 있는 16진수 문자열 포맷으로
  // 변환합니다.
  std::stringstream buff_hex;
  // 문자열 맨 앞이 0x로 시작합니다.
  buff_hex << "0x";

  // 바이너리 데이터를 16진수로 변환해서 16진수 문자열 뒤에
  // 차례대로 이어 붙여줍니다.
  for (int i = 0; i < 11 /*length of data*/; i++) {
    buff_hex << std::hex << std::setfill('0') << std::setw(2) << (int)buff[i];
  }

  // 16진수 문자열로 변환한 바이너리 데이터를 가지고 쿼리문을 만듭니다.
  // 주의할 점은 16진수 문자열은 "" 로 묶지 않고 그 자체를 값으로
  // 사용합니다.
  query << "INSERT INTO BlobTable(data) values (" << buff_hex.str() << ");";

  connections->ExecuteQuery(query.str(),
      [connections](const Ptr<ResultSets> &result_sets,
                    const Mariadb::Error &error)     {
        if (error.code != 0) {
          LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
          return;
        }

        // INSERT 쿼리의 콜백함수에서 마지막 삽입한 ROW 를 SELECT 합니다.
        string query2 =
          "SELECT id, data FROM BlobTable ORDER BY id DESC LIMIT 1;";

        connections->ExecuteQuery(query2, OnQueryExecuted);
      });
}

void OnQueryExecuted(const Ptr<ResultSets> &result_sets,
                     const Mariadb::Error &error) {
  if (error.code != 0) {
    LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
    return;
  }

  while (result_sets->SeekNextResultSet()) {

    while (result_sets->SeekNextRow()) {

      int id = result_sets->GetInt("id");
      const char * data = result_sets->GetBinary("data");
      int data_length = result_sets->GetFieldLength("data");

      std::stringstream bin_to_hex;;
      for (int i = 0; i < data_length; i++) {
        bin_to_hex << std::hex << std::setfill('0') << std::setw(2) << (int)data[i];
      }

      LOG(INFO) << "id : " << id << ", data : " << bin_to_hex.str();
    }
  }
}
C# 에서는 아직  지원되지 않습니다.

Procedure 예제

아래는 다음과 같은 결과값을 생성하는, SELECT 를 직접 하는 대신 Procedure 를 호출해서 결과를 얻어내는 예제입니다.

No: 0, Name: dog, Weight: 12.5
No: 1, Name: cat, Weight: 2.1
No: 2, Name: bird, Weight: 0.5
No: 3, Name: tiger, Weight: 290
Ptr<Mariadb> connections;

void Initialize() {
  connections = Mariadb::Create("tcp://127.0.0.1:3306", "myid", "mypw",
                                "mydb", 5, "utf8", false);
  connections->Initialize();
}

void ExecuteQuery() {
  std::stringstream query;
  query << "CALL ExampleProc();";

  connections->ExecuteQuery(query.str(), OnQueryExecuted);
}

void OnQueryExecuted(const Ptr<ResultSets> &result_sets,
                     const Mariadb::Error &error) {
  if (error.code != 0) {
    LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
    return;
  }

  if (not result_sets->SeekNextResultSet()) {
    LOG(ERROR) << "no result set.";
    return;
  }

  while (result_sets->SeekNextRow()) {
    LOG(INFO) << "No: " << result_sets->GetInt(0)
              << ", Name: " << result_sets->GetString(1)
              << ", Weight: " << result_sets->GetDouble(2);
  }
}
Mariadb connections;

void Initialize() {
  Mariadb connections = Mariadb.Create (
      "tcp://127.0.0.1:3306", "funapi", "funapi", "funapi", 8, "utf8");

  connections.Start();
}

public void ExecuteQuery(Mariadb connections)
{
  connections.ExecuteQuery ("CALL ExampleProc();", OnQueryExecuted);
}

public void OnQueryExecuted(ResultSets result_sets, Mariadb.Error error)
{
  if (error.Code != 0)
  {
    Log.Info ("Error: code={0}, desc={1}", error.Code, error.Desc);
  }

  while (result_sets.SeekNextResultSet ()) {
    while (result_sets.SeekNextRow ()) {
      Log.Info ("No: {0}, Name: {0}, Weight: {0}",
           result_sets.GetInt (0),
           result_sets.GetString(1),
           result_sets.GetDouble(2));
    }
  }
}

Procedure 의 OUTPUT 얻기

이제 프로시져의 OUT 파라미터를 사용하는 방법에 대해서 알아보겠습니다.

아이펀엔진에서 제공하는 MySQL Connector 는 현재 프로시저의 OUT 파라미터를 직접 받을 수 없기 때문에 SQL 세션 변수에 대입해서 SELECT 하는 방법을 사용합니다.

먼저, 출력되는 내용을 확인해 보겠습니다.

p1: 1234, p2: test
Ptr<Mariadb> connections;

void Initialize() {
  connections = Mariadb::Create("tcp://127.0.0.1:3306", "myid", "mypw",
                                "mydb", 5, "utf8", false);
  connections->Initialize();
}

void ExecuteQuery() {
  std::stringstream query;
  query << "CALL ExampleProc2(@p1, @p2);"
        << "SELECT @p1, @p2;";

  connections->ExecuteQuery(query.str(), OnQueryExecuted);
}

void OnQueryExecuted(const Ptr<ResultSets> &result_sets,
                     const Mariadb::Error &error) {
  if (error.code != 0) {
    LOG(ERROR) << "Error: code=" << error.code << ", desc=" << error.desc;
    return;
  }

  if (not result_sets->SeekNextResultSet()) {
    LOG(ERROR) << "no result set.";
    return;
  }

  while (result_sets->SeekNextRow()) {
    LOG(INFO) << "p1: " << result_sets->GetInt(0)
              << ", p2: " << result_sets->GetString(1);
  }
}
Mariadb connections;

void Initialize() {
  Mariadb connections = Mariadb.Create (
      "tcp://127.0.0.1:3306", "funapi", "funapi", "funapi", 8, "utf8");

  connections.Start();
}

void ExecuteQuery()
{
  StringBuilder query = new StringBuilder ();
  query.Append ("CALL ExampleProc2(@p1, @p2);");
  query.Append ("SELECT @p1, @p2;");
  connections.ExecuteQuery (query.ToString(), OnQueryExecuted);
}

public void OnQueryExecuted(ResultSets result_sets, Mariadb.Error error)
{
  if (error.Code != 0)
  {
    Log.Info ("Error: code={0}, desc={1}", error.Code, error.Desc);
  }

  if (!result_sets.SeekNextResultSet ()) {
    Log.Error ("no result set.");
    return;
  }

  while (result_sets.SeekNextRow ()) {
    Log.Info ("p1 : {0}, p2 : {1}",
        result_sets.GetInt (0), result_sets.GetString (1));
  }
}