나만의 작은 도서관

[TIL][C++] 250806 MMO 서버 개발 74일차: SQLRETURN SQLGetDiagRec()함수 본문

Today I Learn

[TIL][C++] 250806 MMO 서버 개발 74일차: SQLRETURN SQLGetDiagRec()함수

pledge24 2025. 8. 7. 04:56
주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다. 

SQLRETURN SQLGetDiagRec()함수

// 가장 최근 오류는 recordCount 번째 레코드
SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, recordCount, 
              sqlState, &nativeError, message, sizeof(message), &textLength);
  • C++에서 DB에게 쿼리를 요청할 때 ODBC를 사용하여 요청할 수 있다. 이때 “SQLExecDirect”함수를 사용하여 요청을 보내는데, 잘못된 요청을 보내거나(unique 특성이 있는 속성이 중복된 행을 보낸다거나) 시스템에 문제가 생겼다면 요청이 수행되지 않고 오류를 뱉어내게 된다.
  • 오류를 뱉어냈다면, 뱉어낸 오류가 무엇인지 알아야 할 것이다. 이때 사용하는 함수가 바로 “SQLGetDiagRec”함수이다.
  • SQLGetDiagRec함수는 ODBC 함수(SQLExecDirect와 같은)를 호출했을 때 그 결과를 담은 “진단 레코드”를 반환하는 함수이다.
    • 여담이지만, DiagRec는 Diagnose Record의 약자인 듯하다.

 

SQLGetDiagRec에 대해서…

  • 특정 진단 레코드 정보를 알고 싶다면 RecNumber를 활용
    • 하나의 ODBC 함수 호출의 결과로 여러 개의 진단 레코드 정보가 올 수 있다. 이때 i번째 진단 레코드 정보를 알고 싶다면, SQLGetDiagRec의 RecNumber를 i로 설정하면 된다. RecNumber가 진단 레코드의 index를 의미하기 때문이다. (참고로 RecNumber는 1부터 시작한다.)
  • RecNumber가 2 이상 되는 경우는 어떤 경우?
-- 여러 오류가 동시에 발생할 수 있는 경우
INSERT INTO table1 VALUES (1, 'test');
INSERT INTO invalid_table VALUES (2, 'test');  -- 테이블 없음 오류
INSERT INTO table1 VALUES ('invalid', 123);    -- 타입 오류

-- 저장 프로시저 내에서 여러 PRINT, RAISERROR 발생
CREATE PROCEDURE test_proc AS
BEGIN
    PRINT 'Warning: This is a test'           -- 정보성 메시지
    RAISERROR('First error', 16, 1)           -- 오류 1
    RAISERROR('Second error', 16, 1)          -- 오류 2
END
  • RecNumber의 숫자와 발생 순서와의 관계
    • ODBC의 진단 레코드는 스택 구조로 관리된다. 따라서, RecNumber를 1로 설정해서 SQLGetDiagRec을 호출하면 가장 오래된(첫 번째) 진단 레코드를 가져오게 된다. (스택이라고 설명하긴 했지만 개인적으론 vector구조의 느낌이 더 있는 듯하다.)
      • RecNumber = 1: 첫 번째 진단 레코드 (가장 먼저 발생한 오류/경고)
      • RecNumber = 2: 두 번째 진단 레코드
      • RecNumber = n: n번째 진단 레코드
    • 만약 SQLGetDiagRec에 RecNumber를 존재하는 진단 레코드 개수를 초과하는 값으로 설정하면 “SQL_NO_DATA”를 반환한다.
SQLRETURN ret;
SQLWCHAR sqlState[6];
SQLINTEGER nativeError;
SQLWCHAR message[256];
SQLSMALLINT textLength;

// 예: 진단 레코드가 2개만 있는데 RecNumber를 5로 설정
ret = SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, 5, sqlState, &nativeError,
                    message, sizeof(message), &textLength);

if (ret == SQL_NO_DATA) {
    printf("No diagnostic record exists at position 5\\n");
}
  • 진단 레코드는 계속해서 누적되지 않는다.
    • 진단 레코드는 커넥션 객체가 SQLExecDirect 함수와 같은 ODBC 함수를 호출할 때마다 초기화된다. 즉, RecNumber가 2 이상의 수를 가질 순 있지만 새로운 요청을 하게 되면 이전의 진단 레코드들은 삭제되고 RecNumber는 1로 되돌아간다.
// 첫 번째 SQL 실행
SQLExecDirect(hstmt, "SELECT * FROM invalid_table", SQL_NTS);
// 이때 RecNumber 1, 2, 3... 생성될 수 있음

// 두 번째 SQL 실행 
SQLExecDirect(hstmt, "INSERT INTO invalid_table2", SQL_NTS);  
// 이전 진단 레코드는 모두 삭제되고, 새로운 RecNumber 1, 2... 시작

 

 

진단 레코드로 오류 확인하는 2가지 방법

 

첫 번째 방법. SQLState

SQLWCHAR sqlState[6];  // 5글자 + null terminator
  • SQLState는 표준화된 에러 코드로, ANSI/ISO SQL 표준 기반의 5자리 코드로 되어있다. 데이터베이스에 종속되지 않는 독립적 코드이기 때문에, Oracle, SQL Server, MySQL 등 여러 데이터베이스에서 동일하게 사용할 수 있다는 장점이 있다.
  • 주요 SQLState 코드들은 아래와 같다.
"00000"  // 성공 (에러 없음)
"01000"  // 경고 (Warning)
"02000"  // 데이터 없음 (No Data)
"07000"  // 동적 SQL 에러
"08000"  // 연결 예외 (Connection Exception)
"21000"  // 카디널리티 위반 (Cardinality Violation)
"22000"  // 데이터 예외 (Data Exception)
"23000"  // 무결성 제약 위반 (Integrity Constraint Violation) - UNIQUE, FK 등
"24000"  // 잘못된 커서 상태
"25000"  // 잘못된 트랜잭션 상태
"42000"  // 구문 에러나 액세스 규칙 위반
"HY000"  // 일반 에러

 

 

두 번째 방법. Native Error

SQLINTEGER nativeError;  // 정수 값
  • Native Error는 DB별 고유 에러 코드로, 각 데이터베이스 벤더가 정의한 코드이다. 특정 데이터베이스에 종속된 에러 코드인 만큼 보다 구체적이고 상세한 오류 정보를 얻을 수 있다.
  • 즉, SQLState는 범용적 오류 코드, Native Error는 DB에 종속된 오류 코드라고 볼 수 있다
  • Native Error이라는 개념은 SQL Server에만 있는 것이 아닌, 대부분의 DB에서 활용하는 듯하다.

SQL Server Native Error 예시

2627  // Violation of UNIQUE KEY constraint
2601  // Cannot insert duplicate key row  
515   // Cannot insert NULL value
547   // Foreign Key constraint conflict
2812  // Could not find stored procedure

 

Oracle Native Error 예시

1     // ORA-00001: unique constraint violated
904   // ORA-00904: invalid identifier
942   // ORA-00942: table or view does not exist
1400  // ORA-01400: cannot insert NULL