나만의 작은 도서관
[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”를 반환한다.
- ODBC의 진단 레코드는 스택 구조로 관리된다. 따라서, RecNumber를 1로 설정해서 SQLGetDiagRec을 호출하면 가장 오래된(첫 번째) 진단 레코드를 가져오게 된다. (스택이라고 설명하긴 했지만 개인적으론 vector구조의 느낌이 더 있는 듯하다.)
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
